From b0b5b522a88e53a4b211a6da9577f5c963ce3f8f Mon Sep 17 00:00:00 2001 From: Rinacm <2653221698@qq.com> Date: Thu, 29 Oct 2020 17:15:11 +0800 Subject: [PATCH] refactor project --- Pixeval/Annotations.cs | 1354 ++++++++++ Pixeval/App.xaml | 318 +++ Pixeval/App.xaml.cs | 128 + Pixeval/AppContext.cs | 68 + Pixeval/Core/AbstractPixivAsyncEnumerable.cs | 67 + Pixeval/Core/AbstractPixivAsyncEnumerator.cs | 50 + Pixeval/Core/BrowsingHistoryAccessor.cs | 143 + ...ateNewFolderForUserDownloadPathProvider.cs | 51 + Pixeval/Core/DefaultDownloadPathProvider.cs | 44 + .../DefaultIllustrationFileNameFormatter.cs | 44 + Pixeval/Core/EnumeratingSchedule.cs | 47 + Pixeval/Core/GalleryAsyncEnumerable.cs | 166 ++ Pixeval/Core/ICancellable.cs | 29 + Pixeval/Core/IDownloadPathProvider.cs | 49 + Pixeval/Core/IPixivAsyncEnumerable.cs | 35 + Pixeval/Core/IQualifier.cs | 27 + Pixeval/Core/ITimelineService.cs | 43 + Pixeval/Core/IllustrationQualification.cs | 56 + Pixeval/Core/IllustrationQualifier.cs | 61 + Pixeval/Core/Managers.cs | 111 + Pixeval/Core/PixivClient.cs | 45 + Pixeval/Core/PixivClientExtension.cs | 99 + Pixeval/Core/PixivHelper.cs | 152 ++ Pixeval/Core/PixivIO.cs | 104 + Pixeval/Core/QueryAsyncEnumerable.cs | 186 ++ Pixeval/Core/RankOption.cs | 136 + Pixeval/Core/RankingAsyncEnumerable.cs | 137 + Pixeval/Core/RecommendAsyncEnumerable.cs | 139 + Pixeval/Core/RecommendIllustratorDeferrer.cs | 71 + Pixeval/Core/RestrictPolicy.cs | 27 + Pixeval/Core/SearchTagMatchOption.cs | 48 + Pixeval/Core/SpotlightQueryAsyncEnumerable.cs | 121 + Pixeval/Core/TrendsAsyncEnumerable.cs | 253 ++ Pixeval/Core/UploadAsyncEnumerable.cs | 136 + Pixeval/Core/UserFollowingAsyncEnumerable.cs | 166 ++ Pixeval/Core/UserPreviewAsyncEnumerable.cs | 126 + Pixeval/Core/UserUpdateAsyncEnumerable.cs | 118 + Pixeval/Core/WindowsUserActivityManager.cs | 137 + Pixeval/Data/IParser.cs | 27 + Pixeval/Data/ViewModel/AutoCompletion.cs | 32 + Pixeval/Data/ViewModel/BrowsingHistory.cs | 58 + Pixeval/Data/ViewModel/ConditionString.cs | 33 + .../ViewModel/DownloadableIllustration.cs | 227 ++ Pixeval/Data/ViewModel/I18nOption.cs | 46 + Pixeval/Data/ViewModel/Illustration.cs | 119 + Pixeval/Data/ViewModel/RankOptionModel.cs | 60 + .../ViewModel/SearchTagMatchOptionModel.cs | 50 + Pixeval/Data/ViewModel/SpotlightArticle.cs | 72 + Pixeval/Data/ViewModel/TrendingTag.cs | 34 + Pixeval/Data/ViewModel/Trends.cs | 67 + Pixeval/Data/ViewModel/User.cs | 46 + .../DnsResolvedHttpClientHandler.cs | 95 + Pixeval/Data/Web/Delegation/DnsResolver.cs | 138 + .../Data/Web/Delegation/HttpClientFactory.cs | 72 + .../Web/Delegation/IHttpRequestHandler.cs | 29 + .../Web/Delegation/PixivApiDnsResolver.cs | 37 + .../Delegation/PixivApiHttpClientHandler.cs | 38 + .../Web/Delegation/PixivHttpRequestHandler.cs | 72 + .../Delegation/PixivImageHttpClientHandler.cs | 35 + Pixeval/Data/Web/HttpResponse.cs | 47 + Pixeval/Data/Web/Protocol/IAppApiProtocol.cs | 64 + .../Data/Web/Protocol/IResolveDnsProtocol.cs | 33 + .../Data/Web/Protocol/ISauceNAOProtocol.cs | 34 + Pixeval/Data/Web/Protocol/ITokenProtocol.cs | 37 + Pixeval/Data/Web/Protocol/IWebApiProtocol.cs | 41 + Pixeval/Data/Web/ProtocolBase.cs | 37 + .../Data/Web/Request/AddBookmarkRequest.cs | 33 + .../Data/Web/Request/AutoCompletionRequest.cs | 33 + .../Data/Web/Request/DeleteBookmarkRequest.cs | 30 + Pixeval/Data/Web/Request/DnsResolveRequest.cs | 42 + .../Data/Web/Request/FollowArtistRequest.cs | 33 + .../Data/Web/Request/PasswordTokenRequest.cs | 45 + .../Request/RecommendIllustratorRequest.cs | 33 + .../Data/Web/Request/RefreshTokenRequest.cs | 42 + .../Data/Web/Request/ToggleR18StateRequest.cs | 45 + .../Data/Web/Request/UnFollowArtistRequest.cs | 30 + .../Web/Request/UserInformationRequest.cs | 33 + .../Web/Response/AutoCompletionResponse.cs | 40 + .../Data/Web/Response/DnsResolveResponse.cs | 76 + .../Data/Web/Response/FollowingResponse.cs | 263 ++ Pixeval/Data/Web/Response/GalleryResponse.cs | 207 ++ .../Data/Web/Response/QueryWorksResponse.cs | 206 ++ Pixeval/Data/Web/Response/RankingResponse.cs | 207 ++ .../Response/RecommendIllustratorResponse.cs | 185 ++ .../Data/Web/Response/RecommendResponse.cs | 210 ++ .../Data/Web/Response/SingleWorkResponse.cs | 164 ++ .../Web/Response/SpotlightArticleResponse.cs | 49 + .../Data/Web/Response/SpotlightResponse.cs | 35 + Pixeval/Data/Web/Response/TokenResponse.cs | 136 + .../Data/Web/Response/TrendingTagResponse.cs | 173 ++ .../Web/Response/UgoiraMetadataResponse.cs | 55 + Pixeval/Data/Web/Response/UploadResponse.cs | 209 ++ .../Web/Response/UserInformationResponse.cs | 198 ++ Pixeval/Data/Web/Response/UserNavResponse.cs | 106 + .../Data/Web/Response/UserUpdateResponse.cs | 207 ++ .../Web/Response/WebApiUserDetailResponse.cs | 54 + Pixeval/FodyWeavers.xml | 3 + Pixeval/FodyWeavers.xsd | 64 + Pixeval/Objects/Caching/CachingPolicy.cs | 27 + Pixeval/Objects/Caching/FileCache.cs | 117 + Pixeval/Objects/Caching/IWeakCacheProvider.cs | 40 + Pixeval/Objects/Caching/MemoryCache.cs | 73 + Pixeval/Objects/Caching/WeakEntry.cs | 74 + Pixeval/Objects/DialogResult.cs | 27 + .../Exceptions/AttributeNotFoundException.cs | 44 + .../Exceptions/AuthenticateFailedException.cs | 44 + .../Exceptions/Logger/ExceptionDumper.cs | 137 + .../Exceptions/QueryNotRespondingException.cs | 44 + .../Exceptions/TokenNotFoundException.cs | 44 + .../Exceptions/TypeMismatchException.cs | 44 + Pixeval/Objects/Generic/Enumerates.cs | 87 + Pixeval/Objects/Generic/Functions.cs | 53 + Pixeval/Objects/Generic/Observable.cs | 64 + Pixeval/Objects/Generic/Tasks.cs | 66 + Pixeval/Objects/I18n/ResourceLocator.cs | 100 + Pixeval/Objects/I18n/StringResources.cs | 1741 ++++++++++++ Pixeval/Objects/Native/WallpaperManager.cs | 79 + Pixeval/Objects/Primitive/Attributes.cs | 46 + Pixeval/Objects/Primitive/InternalIO.cs | 140 + Pixeval/Objects/Primitive/Reflection.cs | 44 + Pixeval/Objects/Primitive/Strings.cs | 103 + Pixeval/Objects/Primitive/UIHelper.cs | 260 ++ .../BoolToCachingPolicyConverter.cs | 40 + .../ValueConverters/DateTimeConverter.cs | 44 + .../DateTimeOffsetConverter.cs | 44 + .../DoubleToPercentConverter.cs | 44 + .../ValueConverters/EnumToStringConverter.cs | 51 + .../IllustSubscriptConverter.cs | 52 + ...IllustrationMatchConditionMaskConverter.cs | 47 + .../InverseBooleanConverter.cs | 39 + .../ValueConverters/MultiCultureConverter.cs | 51 + .../ValueConverters/NumericConverter.cs | 61 + .../QueryR18ToggleButtonIsCheckedConverter.cs | 50 + .../ValueConverters/StringSplitConverter.cs | 45 + .../TagMatchEnumToModelConverter.cs | 56 + .../ValueConverters/TrendsStatConverter.cs | 53 + .../ValueConverters/VisibilityConverter.cs | 99 + Pixeval/Persisting/Authentication.cs | 162 ++ Pixeval/Persisting/Session.cs | 146 + Pixeval/Persisting/Settings.cs | 126 + Pixeval/Pixeval.csproj | 67 + Pixeval/Properties/Annotations.cs | 1432 ++++++++++ Pixeval/Properties/launchSettings.json | 7 + Pixeval/Resources/dqueue.ttf | Bin 0 -> 8028 bytes Pixeval/Resources/iconfont.ttf | Bin 0 -> 16276 bytes Pixeval/Resources/iconfontex.ttf | Bin 0 -> 1992 bytes Pixeval/Resources/logo-only.svg | 1216 +++++++++ Pixeval/Resources/pxlogo.ico | Bin 0 -> 209762 bytes Pixeval/Resources/resx_en-us.xml | 189 ++ Pixeval/Resources/resx_zh-cn.xml | 184 ++ Pixeval/Resources/saucenao.png | Bin 0 -> 5599 bytes Pixeval/UI/MainWindow.xaml | 2394 +++++++++++++++++ Pixeval/UI/MainWindow.xaml.cs | 1218 +++++++++ Pixeval/UI/SignIn.xaml | 153 ++ Pixeval/UI/SignIn.xaml.cs | 106 + Pixeval/UI/UserControls/DownloadQueue.xaml | 423 +++ Pixeval/UI/UserControls/DownloadQueue.xaml.cs | 163 ++ Pixeval/UI/UserControls/IllustPresenter.xaml | 102 + .../UI/UserControls/IllustPresenter.xaml.cs | 180 ++ .../UI/UserControls/IllustTransitioner.xaml | 35 + .../UserControls/IllustTransitioner.xaml.cs | 41 + Pixeval/UI/UserControls/InputBoxControl.xaml | 49 + .../UI/UserControls/InputBoxControl.xaml.cs | 43 + Pixeval/UI/UserControls/MessageDialog.xaml | 52 + Pixeval/UI/UserControls/MessageDialog.xaml.cs | 90 + Pixeval/UI/UserControls/SauceNAOHomePage.xaml | 84 + .../UI/UserControls/SauceNAOHomePage.xaml.cs | 129 + Pixeval/UI/UserControls/SettingsControl.xaml | 269 ++ .../UI/UserControls/SettingsControl.xaml.cs | 91 + .../UI/UserControls/TrendingTagControl.xaml | 132 + .../UserControls/TrendingTagControl.xaml.cs | 66 + .../UserControls/UserPreviewPopupContent.xaml | 155 ++ .../UserPreviewPopupContent.xaml.cs | 65 + Pixeval/app.manifest | 76 + Pixeval/build.ps1 | 1 + 175 files changed, 23938 insertions(+) create mode 100644 Pixeval/Annotations.cs create mode 100644 Pixeval/App.xaml create mode 100644 Pixeval/App.xaml.cs create mode 100644 Pixeval/AppContext.cs create mode 100644 Pixeval/Core/AbstractPixivAsyncEnumerable.cs create mode 100644 Pixeval/Core/AbstractPixivAsyncEnumerator.cs create mode 100644 Pixeval/Core/BrowsingHistoryAccessor.cs create mode 100644 Pixeval/Core/CreateNewFolderForUserDownloadPathProvider.cs create mode 100644 Pixeval/Core/DefaultDownloadPathProvider.cs create mode 100644 Pixeval/Core/DefaultIllustrationFileNameFormatter.cs create mode 100644 Pixeval/Core/EnumeratingSchedule.cs create mode 100644 Pixeval/Core/GalleryAsyncEnumerable.cs create mode 100644 Pixeval/Core/ICancellable.cs create mode 100644 Pixeval/Core/IDownloadPathProvider.cs create mode 100644 Pixeval/Core/IPixivAsyncEnumerable.cs create mode 100644 Pixeval/Core/IQualifier.cs create mode 100644 Pixeval/Core/ITimelineService.cs create mode 100644 Pixeval/Core/IllustrationQualification.cs create mode 100644 Pixeval/Core/IllustrationQualifier.cs create mode 100644 Pixeval/Core/Managers.cs create mode 100644 Pixeval/Core/PixivClient.cs create mode 100644 Pixeval/Core/PixivClientExtension.cs create mode 100644 Pixeval/Core/PixivHelper.cs create mode 100644 Pixeval/Core/PixivIO.cs create mode 100644 Pixeval/Core/QueryAsyncEnumerable.cs create mode 100644 Pixeval/Core/RankOption.cs create mode 100644 Pixeval/Core/RankingAsyncEnumerable.cs create mode 100644 Pixeval/Core/RecommendAsyncEnumerable.cs create mode 100644 Pixeval/Core/RecommendIllustratorDeferrer.cs create mode 100644 Pixeval/Core/RestrictPolicy.cs create mode 100644 Pixeval/Core/SearchTagMatchOption.cs create mode 100644 Pixeval/Core/SpotlightQueryAsyncEnumerable.cs create mode 100644 Pixeval/Core/TrendsAsyncEnumerable.cs create mode 100644 Pixeval/Core/UploadAsyncEnumerable.cs create mode 100644 Pixeval/Core/UserFollowingAsyncEnumerable.cs create mode 100644 Pixeval/Core/UserPreviewAsyncEnumerable.cs create mode 100644 Pixeval/Core/UserUpdateAsyncEnumerable.cs create mode 100644 Pixeval/Core/WindowsUserActivityManager.cs create mode 100644 Pixeval/Data/IParser.cs create mode 100644 Pixeval/Data/ViewModel/AutoCompletion.cs create mode 100644 Pixeval/Data/ViewModel/BrowsingHistory.cs create mode 100644 Pixeval/Data/ViewModel/ConditionString.cs create mode 100644 Pixeval/Data/ViewModel/DownloadableIllustration.cs create mode 100644 Pixeval/Data/ViewModel/I18nOption.cs create mode 100644 Pixeval/Data/ViewModel/Illustration.cs create mode 100644 Pixeval/Data/ViewModel/RankOptionModel.cs create mode 100644 Pixeval/Data/ViewModel/SearchTagMatchOptionModel.cs create mode 100644 Pixeval/Data/ViewModel/SpotlightArticle.cs create mode 100644 Pixeval/Data/ViewModel/TrendingTag.cs create mode 100644 Pixeval/Data/ViewModel/Trends.cs create mode 100644 Pixeval/Data/ViewModel/User.cs create mode 100644 Pixeval/Data/Web/Delegation/DnsResolvedHttpClientHandler.cs create mode 100644 Pixeval/Data/Web/Delegation/DnsResolver.cs create mode 100644 Pixeval/Data/Web/Delegation/HttpClientFactory.cs create mode 100644 Pixeval/Data/Web/Delegation/IHttpRequestHandler.cs create mode 100644 Pixeval/Data/Web/Delegation/PixivApiDnsResolver.cs create mode 100644 Pixeval/Data/Web/Delegation/PixivApiHttpClientHandler.cs create mode 100644 Pixeval/Data/Web/Delegation/PixivHttpRequestHandler.cs create mode 100644 Pixeval/Data/Web/Delegation/PixivImageHttpClientHandler.cs create mode 100644 Pixeval/Data/Web/HttpResponse.cs create mode 100644 Pixeval/Data/Web/Protocol/IAppApiProtocol.cs create mode 100644 Pixeval/Data/Web/Protocol/IResolveDnsProtocol.cs create mode 100644 Pixeval/Data/Web/Protocol/ISauceNAOProtocol.cs create mode 100644 Pixeval/Data/Web/Protocol/ITokenProtocol.cs create mode 100644 Pixeval/Data/Web/Protocol/IWebApiProtocol.cs create mode 100644 Pixeval/Data/Web/ProtocolBase.cs create mode 100644 Pixeval/Data/Web/Request/AddBookmarkRequest.cs create mode 100644 Pixeval/Data/Web/Request/AutoCompletionRequest.cs create mode 100644 Pixeval/Data/Web/Request/DeleteBookmarkRequest.cs create mode 100644 Pixeval/Data/Web/Request/DnsResolveRequest.cs create mode 100644 Pixeval/Data/Web/Request/FollowArtistRequest.cs create mode 100644 Pixeval/Data/Web/Request/PasswordTokenRequest.cs create mode 100644 Pixeval/Data/Web/Request/RecommendIllustratorRequest.cs create mode 100644 Pixeval/Data/Web/Request/RefreshTokenRequest.cs create mode 100644 Pixeval/Data/Web/Request/ToggleR18StateRequest.cs create mode 100644 Pixeval/Data/Web/Request/UnFollowArtistRequest.cs create mode 100644 Pixeval/Data/Web/Request/UserInformationRequest.cs create mode 100644 Pixeval/Data/Web/Response/AutoCompletionResponse.cs create mode 100644 Pixeval/Data/Web/Response/DnsResolveResponse.cs create mode 100644 Pixeval/Data/Web/Response/FollowingResponse.cs create mode 100644 Pixeval/Data/Web/Response/GalleryResponse.cs create mode 100644 Pixeval/Data/Web/Response/QueryWorksResponse.cs create mode 100644 Pixeval/Data/Web/Response/RankingResponse.cs create mode 100644 Pixeval/Data/Web/Response/RecommendIllustratorResponse.cs create mode 100644 Pixeval/Data/Web/Response/RecommendResponse.cs create mode 100644 Pixeval/Data/Web/Response/SingleWorkResponse.cs create mode 100644 Pixeval/Data/Web/Response/SpotlightArticleResponse.cs create mode 100644 Pixeval/Data/Web/Response/SpotlightResponse.cs create mode 100644 Pixeval/Data/Web/Response/TokenResponse.cs create mode 100644 Pixeval/Data/Web/Response/TrendingTagResponse.cs create mode 100644 Pixeval/Data/Web/Response/UgoiraMetadataResponse.cs create mode 100644 Pixeval/Data/Web/Response/UploadResponse.cs create mode 100644 Pixeval/Data/Web/Response/UserInformationResponse.cs create mode 100644 Pixeval/Data/Web/Response/UserNavResponse.cs create mode 100644 Pixeval/Data/Web/Response/UserUpdateResponse.cs create mode 100644 Pixeval/Data/Web/Response/WebApiUserDetailResponse.cs create mode 100644 Pixeval/FodyWeavers.xml create mode 100644 Pixeval/FodyWeavers.xsd create mode 100644 Pixeval/Objects/Caching/CachingPolicy.cs create mode 100644 Pixeval/Objects/Caching/FileCache.cs create mode 100644 Pixeval/Objects/Caching/IWeakCacheProvider.cs create mode 100644 Pixeval/Objects/Caching/MemoryCache.cs create mode 100644 Pixeval/Objects/Caching/WeakEntry.cs create mode 100644 Pixeval/Objects/DialogResult.cs create mode 100644 Pixeval/Objects/Exceptions/AttributeNotFoundException.cs create mode 100644 Pixeval/Objects/Exceptions/AuthenticateFailedException.cs create mode 100644 Pixeval/Objects/Exceptions/Logger/ExceptionDumper.cs create mode 100644 Pixeval/Objects/Exceptions/QueryNotRespondingException.cs create mode 100644 Pixeval/Objects/Exceptions/TokenNotFoundException.cs create mode 100644 Pixeval/Objects/Exceptions/TypeMismatchException.cs create mode 100644 Pixeval/Objects/Generic/Enumerates.cs create mode 100644 Pixeval/Objects/Generic/Functions.cs create mode 100644 Pixeval/Objects/Generic/Observable.cs create mode 100644 Pixeval/Objects/Generic/Tasks.cs create mode 100644 Pixeval/Objects/I18n/ResourceLocator.cs create mode 100644 Pixeval/Objects/I18n/StringResources.cs create mode 100644 Pixeval/Objects/Native/WallpaperManager.cs create mode 100644 Pixeval/Objects/Primitive/Attributes.cs create mode 100644 Pixeval/Objects/Primitive/InternalIO.cs create mode 100644 Pixeval/Objects/Primitive/Reflection.cs create mode 100644 Pixeval/Objects/Primitive/Strings.cs create mode 100644 Pixeval/Objects/Primitive/UIHelper.cs create mode 100644 Pixeval/Objects/ValueConverters/BoolToCachingPolicyConverter.cs create mode 100644 Pixeval/Objects/ValueConverters/DateTimeConverter.cs create mode 100644 Pixeval/Objects/ValueConverters/DateTimeOffsetConverter.cs create mode 100644 Pixeval/Objects/ValueConverters/DoubleToPercentConverter.cs create mode 100644 Pixeval/Objects/ValueConverters/EnumToStringConverter.cs create mode 100644 Pixeval/Objects/ValueConverters/IllustSubscriptConverter.cs create mode 100644 Pixeval/Objects/ValueConverters/IllustrationMatchConditionMaskConverter.cs create mode 100644 Pixeval/Objects/ValueConverters/InverseBooleanConverter.cs create mode 100644 Pixeval/Objects/ValueConverters/MultiCultureConverter.cs create mode 100644 Pixeval/Objects/ValueConverters/NumericConverter.cs create mode 100644 Pixeval/Objects/ValueConverters/QueryR18ToggleButtonIsCheckedConverter.cs create mode 100644 Pixeval/Objects/ValueConverters/StringSplitConverter.cs create mode 100644 Pixeval/Objects/ValueConverters/TagMatchEnumToModelConverter.cs create mode 100644 Pixeval/Objects/ValueConverters/TrendsStatConverter.cs create mode 100644 Pixeval/Objects/ValueConverters/VisibilityConverter.cs create mode 100644 Pixeval/Persisting/Authentication.cs create mode 100644 Pixeval/Persisting/Session.cs create mode 100644 Pixeval/Persisting/Settings.cs create mode 100644 Pixeval/Pixeval.csproj create mode 100644 Pixeval/Properties/Annotations.cs create mode 100644 Pixeval/Properties/launchSettings.json create mode 100644 Pixeval/Resources/dqueue.ttf create mode 100644 Pixeval/Resources/iconfont.ttf create mode 100644 Pixeval/Resources/iconfontex.ttf create mode 100644 Pixeval/Resources/logo-only.svg create mode 100644 Pixeval/Resources/pxlogo.ico create mode 100644 Pixeval/Resources/resx_en-us.xml create mode 100644 Pixeval/Resources/resx_zh-cn.xml create mode 100644 Pixeval/Resources/saucenao.png create mode 100644 Pixeval/UI/MainWindow.xaml create mode 100644 Pixeval/UI/MainWindow.xaml.cs create mode 100644 Pixeval/UI/SignIn.xaml create mode 100644 Pixeval/UI/SignIn.xaml.cs create mode 100644 Pixeval/UI/UserControls/DownloadQueue.xaml create mode 100644 Pixeval/UI/UserControls/DownloadQueue.xaml.cs create mode 100644 Pixeval/UI/UserControls/IllustPresenter.xaml create mode 100644 Pixeval/UI/UserControls/IllustPresenter.xaml.cs create mode 100644 Pixeval/UI/UserControls/IllustTransitioner.xaml create mode 100644 Pixeval/UI/UserControls/IllustTransitioner.xaml.cs create mode 100644 Pixeval/UI/UserControls/InputBoxControl.xaml create mode 100644 Pixeval/UI/UserControls/InputBoxControl.xaml.cs create mode 100644 Pixeval/UI/UserControls/MessageDialog.xaml create mode 100644 Pixeval/UI/UserControls/MessageDialog.xaml.cs create mode 100644 Pixeval/UI/UserControls/SauceNAOHomePage.xaml create mode 100644 Pixeval/UI/UserControls/SauceNAOHomePage.xaml.cs create mode 100644 Pixeval/UI/UserControls/SettingsControl.xaml create mode 100644 Pixeval/UI/UserControls/SettingsControl.xaml.cs create mode 100644 Pixeval/UI/UserControls/TrendingTagControl.xaml create mode 100644 Pixeval/UI/UserControls/TrendingTagControl.xaml.cs create mode 100644 Pixeval/UI/UserControls/UserPreviewPopupContent.xaml create mode 100644 Pixeval/UI/UserControls/UserPreviewPopupContent.xaml.cs create mode 100644 Pixeval/app.manifest create mode 100644 Pixeval/build.ps1 diff --git a/Pixeval/Annotations.cs b/Pixeval/Annotations.cs new file mode 100644 index 00000000..a448d70e --- /dev/null +++ b/Pixeval/Annotations.cs @@ -0,0 +1,1354 @@ +#region Copyright (C) 2019-2020 Dylech30th. All rights reserved. + +// Pixeval - A Strong, Fast and Flexible Pixiv Client +// Copyright (C) 2019-2020 Dylech30th +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as +// published by the Free Software Foundation, either version 3 of the +// License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see . + +#endregion + +using System; + +// ReSharper disable InheritdocConsiderUsage + +#pragma warning disable 1591 +// ReSharper disable UnusedMember.Global +// ReSharper disable MemberCanBePrivate.Global +// ReSharper disable UnusedAutoPropertyAccessor.Global +// ReSharper disable IntroduceOptionalParameters.Global +// ReSharper disable MemberCanBeProtected.Global +// ReSharper disable InconsistentNaming + +namespace Pixeval +{ + /// + /// Indicates that the value of the marked element could be null sometimes, + /// so checking for null is required before its usage. + /// + /// + /// + /// [CanBeNull] object Test() => null; + /// + /// void UseTest() { + /// var p = Test(); + /// var s = p.ToString(); // Warning: Possible 'System.NullReferenceException' + /// } + /// + /// + [AttributeUsage(AttributeTargets.Method | AttributeTargets.Parameter | AttributeTargets.Property | AttributeTargets.Delegate | AttributeTargets.Field | AttributeTargets.Event | AttributeTargets.Class | AttributeTargets.Interface | AttributeTargets.GenericParameter)] + public sealed class CanBeNullAttribute : Attribute + { + } + + /// + /// Indicates that the value of the marked element can never be null. + /// + /// + /// + /// [NotNull] object Foo() { + /// return null; // Warning: Possible 'null' assignment + /// } + /// + /// + [AttributeUsage(AttributeTargets.Method | AttributeTargets.Parameter | AttributeTargets.Property | AttributeTargets.Delegate | AttributeTargets.Field | AttributeTargets.Event | AttributeTargets.Class | AttributeTargets.Interface | AttributeTargets.GenericParameter)] + public sealed class NotNullAttribute : Attribute + { + } + + /// + /// Can be applied to symbols of types derived from IEnumerable as well as to symbols of Task + /// and Lazy classes to indicate that the value of a collection item, of the Task.Result property + /// or of the Lazy.Value property can never be null. + /// + /// + /// + /// public void Foo([ItemNotNull]List<string> books) + /// { + /// foreach (var book in books) { + /// if (book != null) // Warning: Expression is always true + /// Console.WriteLine(book.ToUpper()); + /// } + /// } + /// + /// + [AttributeUsage(AttributeTargets.Method | AttributeTargets.Parameter | AttributeTargets.Property | AttributeTargets.Delegate | AttributeTargets.Field)] + public sealed class ItemNotNullAttribute : Attribute + { + } + + /// + /// Can be applied to symbols of types derived from IEnumerable as well as to symbols of Task + /// and Lazy classes to indicate that the value of a collection item, of the Task.Result property + /// or of the Lazy.Value property can be null. + /// + /// + /// + /// public void Foo([ItemCanBeNull]List<string> books) + /// { + /// foreach (var book in books) + /// { + /// // Warning: Possible 'System.NullReferenceException' + /// Console.WriteLine(book.ToUpper()); + /// } + /// } + /// + /// + [AttributeUsage(AttributeTargets.Method | AttributeTargets.Parameter | AttributeTargets.Property | AttributeTargets.Delegate | AttributeTargets.Field)] + public sealed class ItemCanBeNullAttribute : Attribute + { + } + + /// + /// Indicates that the marked method builds string by the format pattern and (optional) arguments. + /// The parameter, which contains the format string, should be given in constructor. The format string + /// should be in -like form. + /// + /// + /// + /// [StringFormatMethod("message")] + /// void ShowError(string message, params object[] args) { /* do something */ } + /// + /// void Foo() { + /// ShowError("Failed: {0}"); // Warning: Non-existing argument in format string + /// } + /// + /// + [AttributeUsage(AttributeTargets.Constructor | AttributeTargets.Method | AttributeTargets.Property | AttributeTargets.Delegate)] + public sealed class StringFormatMethodAttribute : Attribute + { + /// + /// Specifies which parameter of an annotated method should be treated as the format string + /// + public StringFormatMethodAttribute([NotNull] string formatParameterName) + { + FormatParameterName = formatParameterName; + } + + [NotNull] + public string FormatParameterName { get; } + } + + /// + /// Use this annotation to specify a type that contains static or const fields + /// with values for the annotated property/field/parameter. + /// The specified type will be used to improve completion suggestions. + /// + /// + /// + /// namespace TestNamespace + /// { + /// public class Constants + /// { + /// public static int INT_CONST = 1; + /// public const string STRING_CONST = "1"; + /// } + /// + /// public class Class1 + /// { + /// [ValueProvider("TestNamespace.Constants")] public int myField; + /// public void Foo([ValueProvider("TestNamespace.Constants")] string str) { } + /// + /// public void Test() + /// { + /// Foo(/*try completion here*/);// + /// myField = /*try completion here*/ + /// } + /// } + /// } + /// + /// + [AttributeUsage(AttributeTargets.Parameter | AttributeTargets.Property | AttributeTargets.Field, AllowMultiple = true)] + public sealed class ValueProviderAttribute : Attribute + { + public ValueProviderAttribute([NotNull] string name) + { + Name = name; + } + + [NotNull] + public string Name { get; } + } + + /// + /// Indicates that the function argument should be a string literal and match one + /// of the parameters of the caller function. For example, ReSharper annotates + /// the parameter of . + /// + /// + /// + /// void Foo(string param) { + /// if (param == null) + /// throw new ArgumentNullException("par"); // Warning: Cannot resolve symbol + /// } + /// + /// + [AttributeUsage(AttributeTargets.Parameter)] + public sealed class InvokerParameterNameAttribute : Attribute + { + } + + /// + /// Indicates that the method is contained in a type that implements + /// System.ComponentModel.INotifyPropertyChanged interface and this method + /// is used to notify that some property value changed. + /// + /// + /// The method should be non-static and conform to one of the supported signatures: + /// + /// + /// NotifyChanged(string) + /// + /// + /// NotifyChanged(params string[]) + /// + /// + /// NotifyChanged{T}(Expression{Func{T}}) + /// + /// + /// NotifyChanged{T,U}(Expression{Func{T,U}}) + /// + /// + /// SetProperty{T}(ref T, T, string) + /// + /// + /// + /// + /// + /// public class Foo : INotifyPropertyChanged { + /// public event PropertyChangedEventHandler PropertyChanged; + /// + /// [NotifyPropertyChangedInvocator] + /// protected virtual void NotifyChanged(string propertyName) { ... } + /// + /// string _name; + /// + /// public string Name { + /// get { return _name; } + /// set { _name = value; NotifyChanged("LastName"); /* Warning */ } + /// } + /// } + /// + /// Examples of generated notifications: + /// + /// + /// NotifyChanged("Property") + /// + /// + /// NotifyChanged(() => Property) + /// + /// + /// NotifyChanged((VM x) => x.Property) + /// + /// + /// SetProperty(ref myField, value, "Property") + /// + /// + /// + [AttributeUsage(AttributeTargets.Method)] + public sealed class NotifyPropertyChangedInvocatorAttribute : Attribute + { + public NotifyPropertyChangedInvocatorAttribute() + { + } + + public NotifyPropertyChangedInvocatorAttribute([NotNull] string parameterName) + { + ParameterName = parameterName; + } + + [CanBeNull] + public string ParameterName { get; } + } + + /// + /// Describes dependency between method input and output. + /// + /// + ///

Function Definition Table syntax:

+ /// + /// FDT ::= FDTRow [;FDTRow]* + /// FDTRow ::= Input => Output | Output <= Input + /// Input ::= ParameterName: Value [, Input]* + /// Output ::= [ParameterName: Value]* {halt|stop|void|nothing|Value} + /// Value ::= true | false | null | notnull | canbenull + /// + /// If the method has a single input parameter, its name could be omitted.
+ /// Using halt (or void/nothing, which is the same) for the method output + /// means that the method doesn't return normally (throws or terminates the process).
+ /// Value canbenull is only applicable for output parameters.
+ /// You can use multiple [ContractAnnotation] for each FDT row, or use single attribute + /// with rows separated by semicolon. There is no notion of order rows, all rows are checked + /// for applicability and applied per each program state tracked by the analysis engine.
+ ///
+ /// + /// + /// + /// + /// [ContractAnnotation("=> halt")] + /// public void TerminationMethod() + /// + /// + /// + /// + /// [ContractAnnotation("null <= param:null")] // reverse condition syntax + /// public string GetName(string surname) + /// + /// + /// + /// + /// [ContractAnnotation("s:null => true")] + /// public bool IsNullOrEmpty(string s) // string.IsNullOrEmpty() + /// + /// + /// + /// + /// // A method that returns null if the parameter is null, + /// // and not null if the parameter is not null + /// [ContractAnnotation("null => null; notnull => notnull")] + /// public object Transform(object data) + /// + /// + /// + /// + /// [ContractAnnotation("=> true, result: notnull; => false, result: null")] + /// public bool TryParse(string s, out Person result) + /// + /// + /// + /// + [AttributeUsage(AttributeTargets.Method, AllowMultiple = true)] + public sealed class ContractAnnotationAttribute : Attribute + { + public ContractAnnotationAttribute([NotNull] string contract) : this(contract, false) + { + } + + public ContractAnnotationAttribute([NotNull] string contract, bool forceFullStates) + { + Contract = contract; + ForceFullStates = forceFullStates; + } + + [NotNull] + public string Contract { get; } + + public bool ForceFullStates { get; } + } + + /// + /// Indicates whether the marked element should be localized. + /// + /// + /// + /// [LocalizationRequiredAttribute(true)] + /// class Foo { + /// string str = "my string"; // Warning: Localizable string + /// } + /// + /// + [AttributeUsage(AttributeTargets.All)] + public sealed class LocalizationRequiredAttribute : Attribute + { + public LocalizationRequiredAttribute() : this(true) + { + } + + public LocalizationRequiredAttribute(bool required) + { + Required = required; + } + + public bool Required { get; } + } + + /// + /// Indicates that the value of the marked type (or its derivatives) + /// cannot be compared using '==' or '!=' operators and Equals() + /// should be used instead. However, using '==' or '!=' for comparison + /// with null is always permitted. + /// + /// + /// + /// [CannotApplyEqualityOperator] + /// class NoEquality { } + /// + /// class UsesNoEquality { + /// void Test() { + /// var ca1 = new NoEquality(); + /// var ca2 = new NoEquality(); + /// if (ca1 != null) { // OK + /// bool condition = ca1 == ca2; // Warning + /// } + /// } + /// } + /// + /// + [AttributeUsage(AttributeTargets.Interface | AttributeTargets.Class | AttributeTargets.Struct)] + public sealed class CannotApplyEqualityOperatorAttribute : Attribute + { + } + + /// + /// When applied to a target attribute, specifies a requirement for any type marked + /// with the target attribute to implement or inherit specific type or types. + /// + /// + /// + /// [BaseTypeRequired(typeof(IComponent)] // Specify requirement + /// class ComponentAttribute : Attribute { } + /// + /// [Component] // ComponentAttribute requires implementing IComponent interface + /// class MyComponent : IComponent { } + /// + /// + [AttributeUsage(AttributeTargets.Class, AllowMultiple = true)] + [BaseTypeRequired(typeof(Attribute))] + public sealed class BaseTypeRequiredAttribute : Attribute + { + public BaseTypeRequiredAttribute([NotNull] Type baseType) + { + BaseType = baseType; + } + + [NotNull] + public Type BaseType { get; } + } + + /// + /// Indicates that the marked symbol is used implicitly (e.g. via reflection, in external library), + /// so this symbol will not be reported as unused (as well as by other usage inspections). + /// + [AttributeUsage(AttributeTargets.All)] + public sealed class UsedImplicitlyAttribute : Attribute + { + public UsedImplicitlyAttribute() : this(ImplicitUseKindFlags.Default, ImplicitUseTargetFlags.Default) + { + } + + public UsedImplicitlyAttribute(ImplicitUseKindFlags useKindFlags) : this(useKindFlags, ImplicitUseTargetFlags.Default) + { + } + + public UsedImplicitlyAttribute(ImplicitUseTargetFlags targetFlags) : this(ImplicitUseKindFlags.Default, targetFlags) + { + } + + public UsedImplicitlyAttribute(ImplicitUseKindFlags useKindFlags, ImplicitUseTargetFlags targetFlags) + { + UseKindFlags = useKindFlags; + TargetFlags = targetFlags; + } + + public ImplicitUseKindFlags UseKindFlags { get; } + + public ImplicitUseTargetFlags TargetFlags { get; } + } + + /// + /// Can be applied to attributes, type parameters, and parameters of a type assignable from + /// . + /// When applied to an attribute, the decorated attribute behaves the same as . + /// When applied to a type parameter or to a parameter of type , indicates that the + /// corresponding type + /// is used implicitly. + /// + [AttributeUsage(AttributeTargets.Class | AttributeTargets.GenericParameter | AttributeTargets.Parameter)] + public sealed class MeansImplicitUseAttribute : Attribute + { + public MeansImplicitUseAttribute() : this(ImplicitUseKindFlags.Default, ImplicitUseTargetFlags.Default) + { + } + + public MeansImplicitUseAttribute(ImplicitUseKindFlags useKindFlags) : this(useKindFlags, ImplicitUseTargetFlags.Default) + { + } + + public MeansImplicitUseAttribute(ImplicitUseTargetFlags targetFlags) : this(ImplicitUseKindFlags.Default, targetFlags) + { + } + + public MeansImplicitUseAttribute(ImplicitUseKindFlags useKindFlags, ImplicitUseTargetFlags targetFlags) + { + UseKindFlags = useKindFlags; + TargetFlags = targetFlags; + } + + [UsedImplicitly] + public ImplicitUseKindFlags UseKindFlags { get; } + + [UsedImplicitly] + public ImplicitUseTargetFlags TargetFlags { get; } + } + + /// + /// Specify the details of implicitly used symbol when it is marked + /// with or . + /// + [Flags] + public enum ImplicitUseKindFlags + { + Default = Access | Assign | InstantiatedWithFixedConstructorSignature, + + /// Only entity marked with attribute considered used. + Access = 1, + + /// Indicates implicit assignment to a member. + Assign = 2, + + /// + /// Indicates implicit instantiation of a type with fixed constructor signature. + /// That means any unused constructor parameters won't be reported as such. + /// + InstantiatedWithFixedConstructorSignature = 4, + + /// Indicates implicit instantiation of a type. + InstantiatedNoFixedConstructorSignature = 8 + } + + /// + /// Specify what is considered to be used implicitly when marked + /// with or . + /// + [Flags] + public enum ImplicitUseTargetFlags + { + Default = Itself, Itself = 1, + + /// Members of entity marked with attribute are considered used. + Members = 2, + + /// Entity marked with attribute and all its members considered used. + WithMembers = Itself | Members + } + + /// + /// This attribute is intended to mark publicly available API + /// which should not be removed and so is treated as used. + /// + [MeansImplicitUse(ImplicitUseTargetFlags.WithMembers)] + public sealed class PublicAPIAttribute : Attribute + { + public PublicAPIAttribute() + { + } + + public PublicAPIAttribute([NotNull] string comment) + { + Comment = comment; + } + + [CanBeNull] + public string Comment { get; } + } + + /// + /// Tells code analysis engine if the parameter is completely handled when the invoked method is on stack. + /// If the parameter is a delegate, indicates that delegate is executed while the method is executed. + /// If the parameter is an enumerable, indicates that it is enumerated while the method is executed. + /// + [AttributeUsage(AttributeTargets.Parameter)] + public sealed class InstantHandleAttribute : Attribute + { + } + + /// + /// Indicates that a method does not make any observable state changes. + /// The same as System.Diagnostics.Contracts.PureAttribute. + /// + /// + /// + /// [Pure] int Multiply(int x, int y) => x * y; + /// + /// void M() { + /// Multiply(123, 42); // Waring: Return value of pure method is not used + /// } + /// + /// + [AttributeUsage(AttributeTargets.Method)] + public sealed class PureAttribute : Attribute + { + } + + /// + /// Indicates that the return value of the method invocation must be used. + /// + /// + /// Methods decorated with this attribute (in contrast to pure methods) might change state, + /// but make no sense without using their return value.
+ /// Similarly to , this attribute + /// will help detecting usages of the method when the return value in not used. + /// Additionally, you can optionally specify a custom message, which will be used when showing warnings, e.g. + /// [MustUseReturnValue("Use the return value to...")]. + ///
+ [AttributeUsage(AttributeTargets.Method)] + public sealed class MustUseReturnValueAttribute : Attribute + { + public MustUseReturnValueAttribute() + { + } + + public MustUseReturnValueAttribute([NotNull] string justification) + { + Justification = justification; + } + + [CanBeNull] + public string Justification { get; } + } + + /// + /// Indicates the type member or parameter of some type, that should be used instead of all other ways + /// to get the value of that type. This annotation is useful when you have some "context" value evaluated + /// and stored somewhere, meaning that all other ways to get this value must be consolidated with existing one. + /// + /// + /// + /// class Foo { + /// [ProvidesContext] IBarService _barService = ...; + /// + /// void ProcessNode(INode node) { + /// DoSomething(node, node.GetGlobalServices().Bar); + /// // ^ Warning: use value of '_barService' field + /// } + /// } + /// + /// + [AttributeUsage(AttributeTargets.Field | AttributeTargets.Property | AttributeTargets.Parameter | AttributeTargets.Method | AttributeTargets.Class | AttributeTargets.Interface | AttributeTargets.Struct | AttributeTargets.GenericParameter)] + public sealed class ProvidesContextAttribute : Attribute + { + } + + /// + /// Indicates that a parameter is a path to a file or a folder within a web project. + /// Path can be relative or absolute, starting from web root (~). + /// + [AttributeUsage(AttributeTargets.Parameter)] + public sealed class PathReferenceAttribute : Attribute + { + public PathReferenceAttribute() + { + } + + public PathReferenceAttribute([NotNull] [PathReference] string basePath) + { + BasePath = basePath; + } + + [CanBeNull] + public string BasePath { get; } + } + + /// + /// An extension method marked with this attribute is processed by code completion + /// as a 'Source Template'. When the extension method is completed over some expression, its source code + /// is automatically expanded like a template at call site. + /// + /// + /// Template method body can contain valid source code and/or special comments starting with '$'. + /// Text inside these comments is added as source code when the template is applied. Template parameters + /// can be used either as additional method parameters or as identifiers wrapped in two '$' signs. + /// Use the attribute to specify macros for parameters. + /// + /// + /// In this example, the 'forEach' method is a source template available over all values + /// of enumerable types, producing ordinary C# 'foreach' statement and placing caret inside block: + /// + /// [SourceTemplate] + /// public static void forEach<T>(this IEnumerable<T> xs) { + /// foreach (var x in xs) { + /// //$ $END$ + /// } + /// } + /// + /// + [AttributeUsage(AttributeTargets.Method)] + public sealed class SourceTemplateAttribute : Attribute + { + } + + /// + /// Allows specifying a macro for a parameter of a source template. + /// + /// + /// You can apply the attribute on the whole method or on any of its additional parameters. The macro expression + /// is defined in the property. When applied on a method, the target + /// template parameter is defined in the property. To apply the macro silently + /// for the parameter, set the property value = -1. + /// + /// + /// Applying the attribute on a source template method: + /// + /// [SourceTemplate, Macro(Target = "item", Expression = "suggestVariableName()")] + /// public static void forEach<T>(this IEnumerable<T> collection) { + /// foreach (var item in collection) { + /// //$ $END$ + /// } + /// } + /// + /// Applying the attribute on a template method parameter: + /// + /// [SourceTemplate] + /// public static void something(this Entity x, [Macro(Expression = "guid()", Editable = -1)] string newguid) { + /// /*$ var $x$Id = "$newguid$" + x.ToString(); + /// x.DoSomething($x$Id); */ + /// } + /// + /// + [AttributeUsage(AttributeTargets.Parameter | AttributeTargets.Method, AllowMultiple = true)] + public sealed class MacroAttribute : Attribute + { + /// + /// Allows specifying a macro that will be executed for a source template + /// parameter when the template is expanded. + /// + [CanBeNull] + public string Expression { get; set; } + + /// + /// Allows specifying which occurrence of the target parameter becomes editable when the template is deployed. + /// + /// + /// If the target parameter is used several times in the template, only one occurrence becomes editable; + /// other occurrences are changed synchronously. To specify the zero-based index of the editable occurrence, + /// use values >= 0. To make the parameter non-editable when the template is expanded, use -1. + /// + public int Editable { get; set; } + + /// + /// Identifies the target parameter of a source template if the + /// is applied on a template method. + /// + [CanBeNull] + public string Target { get; set; } + } + + [AttributeUsage(AttributeTargets.Assembly | AttributeTargets.Field | AttributeTargets.Property, AllowMultiple = true)] + public sealed class AspMvcAreaMasterLocationFormatAttribute : Attribute + { + public AspMvcAreaMasterLocationFormatAttribute([NotNull] string format) + { + Format = format; + } + + [NotNull] + public string Format { get; } + } + + [AttributeUsage(AttributeTargets.Assembly | AttributeTargets.Field | AttributeTargets.Property, AllowMultiple = true)] + public sealed class AspMvcAreaPartialViewLocationFormatAttribute : Attribute + { + public AspMvcAreaPartialViewLocationFormatAttribute([NotNull] string format) + { + Format = format; + } + + [NotNull] + public string Format { get; } + } + + [AttributeUsage(AttributeTargets.Assembly | AttributeTargets.Field | AttributeTargets.Property, AllowMultiple = true)] + public sealed class AspMvcAreaViewLocationFormatAttribute : Attribute + { + public AspMvcAreaViewLocationFormatAttribute([NotNull] string format) + { + Format = format; + } + + [NotNull] + public string Format { get; } + } + + [AttributeUsage(AttributeTargets.Assembly | AttributeTargets.Field | AttributeTargets.Property, AllowMultiple = true)] + public sealed class AspMvcMasterLocationFormatAttribute : Attribute + { + public AspMvcMasterLocationFormatAttribute([NotNull] string format) + { + Format = format; + } + + [NotNull] + public string Format { get; } + } + + [AttributeUsage(AttributeTargets.Assembly | AttributeTargets.Field | AttributeTargets.Property, AllowMultiple = true)] + public sealed class AspMvcPartialViewLocationFormatAttribute : Attribute + { + public AspMvcPartialViewLocationFormatAttribute([NotNull] string format) + { + Format = format; + } + + [NotNull] + public string Format { get; } + } + + [AttributeUsage(AttributeTargets.Assembly | AttributeTargets.Field | AttributeTargets.Property, AllowMultiple = true)] + public sealed class AspMvcViewLocationFormatAttribute : Attribute + { + public AspMvcViewLocationFormatAttribute([NotNull] string format) + { + Format = format; + } + + [NotNull] + public string Format { get; } + } + + /// + /// ASP.NET MVC attribute. If applied to a parameter, indicates that the parameter + /// is an MVC action. If applied to a method, the MVC action name is calculated + /// implicitly from the context. Use this attribute for custom wrappers similar to + /// System.Web.Mvc.Html.ChildActionExtensions.RenderAction(HtmlHelper, String). + /// + [AttributeUsage(AttributeTargets.Parameter | AttributeTargets.Method | AttributeTargets.Field | AttributeTargets.Property)] + public sealed class AspMvcActionAttribute : Attribute + { + public AspMvcActionAttribute() + { + } + + public AspMvcActionAttribute([NotNull] string anonymousProperty) + { + AnonymousProperty = anonymousProperty; + } + + [CanBeNull] + public string AnonymousProperty { get; } + } + + /// + /// ASP.NET MVC attribute. Indicates that the marked parameter is an MVC area. + /// Use this attribute for custom wrappers similar to + /// System.Web.Mvc.Html.ChildActionExtensions.RenderAction(HtmlHelper, String). + /// + [AttributeUsage(AttributeTargets.Parameter | AttributeTargets.Field | AttributeTargets.Property)] + public sealed class AspMvcAreaAttribute : Attribute + { + public AspMvcAreaAttribute() + { + } + + public AspMvcAreaAttribute([NotNull] string anonymousProperty) + { + AnonymousProperty = anonymousProperty; + } + + [CanBeNull] + public string AnonymousProperty { get; } + } + + /// + /// ASP.NET MVC attribute. If applied to a parameter, indicates that the parameter is + /// an MVC controller. If applied to a method, the MVC controller name is calculated + /// implicitly from the context. Use this attribute for custom wrappers similar to + /// System.Web.Mvc.Html.ChildActionExtensions.RenderAction(HtmlHelper, String, String). + /// + [AttributeUsage(AttributeTargets.Parameter | AttributeTargets.Method | AttributeTargets.Field | AttributeTargets.Property)] + public sealed class AspMvcControllerAttribute : Attribute + { + public AspMvcControllerAttribute() + { + } + + public AspMvcControllerAttribute([NotNull] string anonymousProperty) + { + AnonymousProperty = anonymousProperty; + } + + [CanBeNull] + public string AnonymousProperty { get; } + } + + /// + /// ASP.NET MVC attribute. Indicates that the marked parameter is an MVC Master. Use this attribute + /// for custom wrappers similar to System.Web.Mvc.Controller.View(String, String). + /// + [AttributeUsage(AttributeTargets.Parameter | AttributeTargets.Field | AttributeTargets.Property)] + public sealed class AspMvcMasterAttribute : Attribute + { + } + + /// + /// ASP.NET MVC attribute. Indicates that the marked parameter is an MVC model type. Use this attribute + /// for custom wrappers similar to System.Web.Mvc.Controller.View(String, Object). + /// + [AttributeUsage(AttributeTargets.Parameter)] + public sealed class AspMvcModelTypeAttribute : Attribute + { + } + + /// + /// ASP.NET MVC attribute. If applied to a parameter, indicates that the parameter is an MVC + /// partial view. If applied to a method, the MVC partial view name is calculated implicitly + /// from the context. Use this attribute for custom wrappers similar to + /// System.Web.Mvc.Html.RenderPartialExtensions.RenderPartial(HtmlHelper, String). + /// + [AttributeUsage(AttributeTargets.Parameter | AttributeTargets.Method | AttributeTargets.Field | AttributeTargets.Property)] + public sealed class AspMvcPartialViewAttribute : Attribute + { + } + + /// + /// ASP.NET MVC attribute. Allows disabling inspections for MVC views within a class or a method. + /// + [AttributeUsage(AttributeTargets.Class | AttributeTargets.Method)] + public sealed class AspMvcSuppressViewErrorAttribute : Attribute + { + } + + /// + /// ASP.NET MVC attribute. Indicates that a parameter is an MVC display template. + /// Use this attribute for custom wrappers similar to + /// System.Web.Mvc.Html.DisplayExtensions.DisplayForModel(HtmlHelper, String). + /// + [AttributeUsage(AttributeTargets.Parameter | AttributeTargets.Field | AttributeTargets.Property)] + public sealed class AspMvcDisplayTemplateAttribute : Attribute + { + } + + /// + /// ASP.NET MVC attribute. Indicates that the marked parameter is an MVC editor template. + /// Use this attribute for custom wrappers similar to + /// System.Web.Mvc.Html.EditorExtensions.EditorForModel(HtmlHelper, String). + /// + [AttributeUsage(AttributeTargets.Parameter | AttributeTargets.Field | AttributeTargets.Property)] + public sealed class AspMvcEditorTemplateAttribute : Attribute + { + } + + /// + /// ASP.NET MVC attribute. Indicates that the marked parameter is an MVC template. + /// Use this attribute for custom wrappers similar to + /// System.ComponentModel.DataAnnotations.UIHintAttribute(System.String). + /// + [AttributeUsage(AttributeTargets.Parameter | AttributeTargets.Field | AttributeTargets.Property)] + public sealed class AspMvcTemplateAttribute : Attribute + { + } + + /// + /// ASP.NET MVC attribute. If applied to a parameter, indicates that the parameter + /// is an MVC view component. If applied to a method, the MVC view name is calculated implicitly + /// from the context. Use this attribute for custom wrappers similar to + /// System.Web.Mvc.Controller.View(Object). + /// + [AttributeUsage(AttributeTargets.Parameter | AttributeTargets.Method | AttributeTargets.Field | AttributeTargets.Property)] + public sealed class AspMvcViewAttribute : Attribute + { + } + + /// + /// ASP.NET MVC attribute. If applied to a parameter, indicates that the parameter + /// is an MVC view component name. + /// + [AttributeUsage(AttributeTargets.Parameter | AttributeTargets.Field | AttributeTargets.Property)] + public sealed class AspMvcViewComponentAttribute : Attribute + { + } + + /// + /// ASP.NET MVC attribute. If applied to a parameter, indicates that the parameter + /// is an MVC view component view. If applied to a method, the MVC view component view name is default. + /// + [AttributeUsage(AttributeTargets.Parameter | AttributeTargets.Method | AttributeTargets.Field | AttributeTargets.Property)] + public sealed class AspMvcViewComponentViewAttribute : Attribute + { + } + + /// + /// ASP.NET MVC attribute. When applied to a parameter of an attribute, + /// indicates that this parameter is an MVC action name. + /// + /// + /// + /// [ActionName("Foo")] + /// public ActionResult Login(string returnUrl) { + /// ViewBag.ReturnUrl = Url.Action("Foo"); // OK + /// return RedirectToAction("Bar"); // Error: Cannot resolve action + /// } + /// + /// + [AttributeUsage(AttributeTargets.Parameter | AttributeTargets.Property)] + public sealed class AspMvcActionSelectorAttribute : Attribute + { + } + + [AttributeUsage(AttributeTargets.Parameter | AttributeTargets.Property | AttributeTargets.Field)] + public sealed class HtmlElementAttributesAttribute : Attribute + { + public HtmlElementAttributesAttribute() + { + } + + public HtmlElementAttributesAttribute([NotNull] string name) + { + Name = name; + } + + [CanBeNull] + public string Name { get; } + } + + [AttributeUsage(AttributeTargets.Parameter | AttributeTargets.Field | AttributeTargets.Property)] + public sealed class HtmlAttributeValueAttribute : Attribute + { + public HtmlAttributeValueAttribute([NotNull] string name) + { + Name = name; + } + + [NotNull] + public string Name { get; } + } + + /// + /// Razor attribute. Indicates that the marked parameter or method is a Razor section. + /// Use this attribute for custom wrappers similar to + /// System.Web.WebPages.WebPageBase.RenderSection(String). + /// + [AttributeUsage(AttributeTargets.Parameter | AttributeTargets.Method)] + public sealed class RazorSectionAttribute : Attribute + { + } + + /// + /// Indicates how method, constructor invocation, or property access + /// over collection type affects the contents of the collection. + /// Use to specify the access type. + /// + /// + /// Using this attribute only makes sense if all collection methods are marked with this attribute. + /// + /// + /// + /// public class MyStringCollection : List<string> + /// { + /// [CollectionAccess(CollectionAccessType.Read)] + /// public string GetFirstString() + /// { + /// return this.ElementAt(0); + /// } + /// } + /// class Test + /// { + /// public void Foo() + /// { + /// // Warning: Contents of the collection is never updated + /// var col = new MyStringCollection(); + /// string x = col.GetFirstString(); + /// } + /// } + /// + /// + [AttributeUsage(AttributeTargets.Method | AttributeTargets.Constructor | AttributeTargets.Property)] + public sealed class CollectionAccessAttribute : Attribute + { + public CollectionAccessAttribute(CollectionAccessType collectionAccessType) + { + CollectionAccessType = collectionAccessType; + } + + public CollectionAccessType CollectionAccessType { get; } + } + + /// + /// Provides a value for the to define + /// how the collection method invocation affects the contents of the collection. + /// + [Flags] + public enum CollectionAccessType + { + /// Method does not use or modify content of the collection. + None = 0, + + /// Method only reads content of the collection but does not modify it. + Read = 1, + + /// Method can change content of the collection but does not add new elements. + ModifyExistingContent = 2, + + /// Method can add new elements to the collection. + UpdatedContent = ModifyExistingContent | 4 + } + + /// + /// Indicates that the marked method is assertion method, i.e. it halts the control flow if + /// one of the conditions is satisfied. To set the condition, mark one of the parameters with + /// attribute. + /// + [AttributeUsage(AttributeTargets.Method)] + public sealed class AssertionMethodAttribute : Attribute + { + } + + /// + /// Indicates the condition parameter of the assertion method. The method itself should be + /// marked by attribute. The mandatory argument of + /// the attribute is the assertion type. + /// + [AttributeUsage(AttributeTargets.Parameter)] + public sealed class AssertionConditionAttribute : Attribute + { + public AssertionConditionAttribute(AssertionConditionType conditionType) + { + ConditionType = conditionType; + } + + public AssertionConditionType ConditionType { get; } + } + + /// + /// Specifies assertion type. If the assertion method argument satisfies the condition, + /// then the execution continues. Otherwise, execution is assumed to be halted. + /// + public enum AssertionConditionType + { + /// Marked parameter should be evaluated to true. + IS_TRUE = 0, + + /// Marked parameter should be evaluated to false. + IS_FALSE = 1, + + /// Marked parameter should be evaluated to null value. + IS_NULL = 2, + + /// Marked parameter should be evaluated to not null value. + IS_NOT_NULL = 3 + } + + /// + /// Indicates that the marked method unconditionally terminates control flow execution. + /// For example, it could unconditionally throw exception. + /// + [Obsolete("Use [ContractAnnotation('=> halt')] instead")] + [AttributeUsage(AttributeTargets.Method)] + public sealed class TerminatesProgramAttribute : Attribute + { + } + + /// + /// Indicates that method is pure LINQ method, with postponed enumeration (like Enumerable.Select, + /// .Where). This annotation allows inference of [InstantHandle] annotation for parameters + /// of delegate type by analyzing LINQ method chains. + /// + [AttributeUsage(AttributeTargets.Method)] + public sealed class LinqTunnelAttribute : Attribute + { + } + + /// + /// Indicates that IEnumerable passed as a parameter is not enumerated. + /// Use this annotation to suppress the 'Possible multiple enumeration of IEnumerable' inspection. + /// + /// + /// + /// static void ThrowIfNull<T>([NoEnumeration] T v, string n) where T : class + /// { + /// // custom check for null but no enumeration + /// } + /// + /// void Foo(IEnumerable<string> values) + /// { + /// ThrowIfNull(values, nameof(values)); + /// var x = values.ToList(); // No warnings about multiple enumeration + /// } + /// + /// + [AttributeUsage(AttributeTargets.Parameter)] + public sealed class NoEnumerationAttribute : Attribute + { + } + + /// + /// Indicates that the marked parameter is a regular expression pattern. + /// + [AttributeUsage(AttributeTargets.Parameter)] + public sealed class RegexPatternAttribute : Attribute + { + } + + /// + /// Prevents the Member Reordering feature from tossing members of the marked class. + /// + /// + /// The attribute must be mentioned in your member reordering patterns. + /// + [AttributeUsage(AttributeTargets.Class | AttributeTargets.Interface | AttributeTargets.Struct | AttributeTargets.Enum)] + public sealed class NoReorderAttribute : Attribute + { + } + + /// + /// XAML attribute. Indicates the type that has ItemsSource property and should be treated + /// as ItemsControl-derived type, to enable inner items DataContext type resolve. + /// + [AttributeUsage(AttributeTargets.Class)] + public sealed class XamlItemsControlAttribute : Attribute + { + } + + /// + /// XAML attribute. Indicates the property of some BindingBase-derived type, that + /// is used to bind some item of ItemsControl-derived type. This annotation will + /// enable the DataContext type resolve for XAML bindings for such properties. + /// + /// + /// Property should have the tree ancestor of the ItemsControl type or + /// marked with the attribute. + /// + [AttributeUsage(AttributeTargets.Property)] + public sealed class XamlItemBindingOfItemsControlAttribute : Attribute + { + } + + [AttributeUsage(AttributeTargets.Class, AllowMultiple = true)] + public sealed class AspChildControlTypeAttribute : Attribute + { + public AspChildControlTypeAttribute([NotNull] string tagName, [NotNull] Type controlType) + { + TagName = tagName; + ControlType = controlType; + } + + [NotNull] + public string TagName { get; } + + [NotNull] + public Type ControlType { get; } + } + + [AttributeUsage(AttributeTargets.Property | AttributeTargets.Method)] + public sealed class AspDataFieldAttribute : Attribute + { + } + + [AttributeUsage(AttributeTargets.Property | AttributeTargets.Method)] + public sealed class AspDataFieldsAttribute : Attribute + { + } + + [AttributeUsage(AttributeTargets.Property)] + public sealed class AspMethodPropertyAttribute : Attribute + { + } + + [AttributeUsage(AttributeTargets.Class, AllowMultiple = true)] + public sealed class AspRequiredAttributeAttribute : Attribute + { + public AspRequiredAttributeAttribute([NotNull] string attribute) + { + Attribute = attribute; + } + + [NotNull] + public string Attribute { get; } + } + + [AttributeUsage(AttributeTargets.Property)] + public sealed class AspTypePropertyAttribute : Attribute + { + public AspTypePropertyAttribute(bool createConstructorReferences) + { + CreateConstructorReferences = createConstructorReferences; + } + + public bool CreateConstructorReferences { get; } + } + + [AttributeUsage(AttributeTargets.Assembly, AllowMultiple = true)] + public sealed class RazorImportNamespaceAttribute : Attribute + { + public RazorImportNamespaceAttribute([NotNull] string name) + { + Name = name; + } + + [NotNull] + public string Name { get; } + } + + [AttributeUsage(AttributeTargets.Assembly, AllowMultiple = true)] + public sealed class RazorInjectionAttribute : Attribute + { + public RazorInjectionAttribute([NotNull] string type, [NotNull] string fieldName) + { + Type = type; + FieldName = fieldName; + } + + [NotNull] + public string Type { get; } + + [NotNull] + public string FieldName { get; } + } + + [AttributeUsage(AttributeTargets.Assembly, AllowMultiple = true)] + public sealed class RazorDirectiveAttribute : Attribute + { + public RazorDirectiveAttribute([NotNull] string directive) + { + Directive = directive; + } + + [NotNull] + public string Directive { get; } + } + + [AttributeUsage(AttributeTargets.Assembly, AllowMultiple = true)] + public sealed class RazorPageBaseTypeAttribute : Attribute + { + public RazorPageBaseTypeAttribute([NotNull] string baseType) + { + BaseType = baseType; + } + + public RazorPageBaseTypeAttribute([NotNull] string baseType, string pageName) + { + BaseType = baseType; + PageName = pageName; + } + + [NotNull] + public string BaseType { get; } + + [CanBeNull] + public string PageName { get; } + } + + [AttributeUsage(AttributeTargets.Method)] + public sealed class RazorHelperCommonAttribute : Attribute + { + } + + [AttributeUsage(AttributeTargets.Property)] + public sealed class RazorLayoutAttribute : Attribute + { + } + + [AttributeUsage(AttributeTargets.Method)] + public sealed class RazorWriteLiteralMethodAttribute : Attribute + { + } + + [AttributeUsage(AttributeTargets.Method)] + public sealed class RazorWriteMethodAttribute : Attribute + { + } + + [AttributeUsage(AttributeTargets.Parameter)] + public sealed class RazorWriteMethodParameterAttribute : Attribute + { + } +} \ No newline at end of file diff --git a/Pixeval/App.xaml b/Pixeval/App.xaml new file mode 100644 index 00000000..fc7ad856 --- /dev/null +++ b/Pixeval/App.xaml @@ -0,0 +1,318 @@ + + + + + + + + + + + + + + + + + + 2 + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/Pixeval/App.xaml.cs b/Pixeval/App.xaml.cs new file mode 100644 index 00000000..e9d4ed96 --- /dev/null +++ b/Pixeval/App.xaml.cs @@ -0,0 +1,128 @@ +#region Copyright (C) 2019-2020 Dylech30th. All rights reserved. + +// Pixeval - A Strong, Fast and Flexible Pixiv Client +// Copyright (C) 2019-2020 Dylech30th +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as +// published by the Free Software Foundation, either version 3 of the +// License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see . + +#endregion + + +using System; +using System.Diagnostics; +using System.IO; +using System.Threading; +using System.Threading.Tasks; +using System.Windows; +using Pixeval.Core; +using Pixeval.Objects.I18n; +using Pixeval.Objects.Primitive; +using Pixeval.Persisting; +#if RELEASE +using System.Net; +using Pixeval.Objects.Exceptions.Logger; + +#endif + +namespace Pixeval +{ + public partial class App + { + private static readonly Mutex UniqueMutex = new Mutex(true, "Pixeval Mutex"); + + public App() + { + if (Dispatcher != null) + { + Dispatcher.UnhandledException += (sender, args) => DispatcherOnUnhandledException(args.Exception); + } + AppDomain.CurrentDomain.UnhandledException += (sender, args) => DispatcherOnUnhandledException((Exception) args.ExceptionObject); + TaskScheduler.UnobservedTaskException += (sender, args) => DispatcherOnUnhandledException(args.Exception); + } + + private static void DispatcherOnUnhandledException(Exception e) + { +#if RELEASE + if (e is WebException || e is TaskCanceledException) + { + return; + } + ExceptionDumper.WriteException(e); +#elif DEBUG + throw e; +#endif + } + + protected override async void OnStartup(StartupEventArgs e) + { + InitializeFolders(); + CheckMultipleProcess(); + await RestoreSettings(); + await CheckUpdate(); + base.OnStartup(e); + } + + private static void InitializeFolders() + { + Directory.CreateDirectory(AppContext.ProjectFolder); + Directory.CreateDirectory(AppContext.SettingsFolder); + Directory.CreateDirectory(AppContext.ExceptionReportFolder); + } + + private static void CheckMultipleProcess() + { + if (!UniqueMutex.WaitOne(0, false)) + { + MessageBox.Show(AkaI18N.MultiplePixevalInstanceDetected, AkaI18N.MultiplePixevalInstanceDetectedTitle, MessageBoxButton.OK, MessageBoxImage.Error); + Environment.Exit(-1); + } + } + + private static async Task CheckUpdate() + { + if (await AppContext.UpdateAvailable() && MessageBox.Show(AkaI18N.PixevalUpdateAvailable, AkaI18N.PixevalUpdateAvailableTitle, MessageBoxButton.YesNo, MessageBoxImage.Information) == MessageBoxResult.Yes) + { + Process.Start(@"updater\Pixeval.Updater.exe"); + Environment.Exit(0); + } + } + + private static async Task RestoreSettings() + { + await Settings.Restore(); + BrowsingHistoryAccessor.GlobalLifeTimeScope = new BrowsingHistoryAccessor(200, AppContext.BrowseHistoryDatabase); + } + + protected override async void OnExit(ExitEventArgs e) + { + await Settings.Global.Store(); + if (Session.Current != null && !Session.Current.AccessToken.IsNullOrEmpty()) + { + await Session.Current.Store(); + } + if (File.Exists(AppContext.BrowseHistoryDatabase)) + { + BrowsingHistoryAccessor.GlobalLifeTimeScope.SetWritable(); + BrowsingHistoryAccessor.GlobalLifeTimeScope.Rewrite(); + BrowsingHistoryAccessor.GlobalLifeTimeScope.Dispose(); + } + else + { + BrowsingHistoryAccessor.GlobalLifeTimeScope.EmergencyRewrite(); + } + + base.OnExit(e); + } + } +} \ No newline at end of file diff --git a/Pixeval/AppContext.cs b/Pixeval/AppContext.cs new file mode 100644 index 00000000..c820f486 --- /dev/null +++ b/Pixeval/AppContext.cs @@ -0,0 +1,68 @@ +#region Copyright (C) 2019-2020 Dylech30th. All rights reserved. + +// Pixeval - A Strong, Fast and Flexible Pixiv Client +// Copyright (C) 2019-2020 Dylech30th +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as +// published by the Free Software Foundation, either version 3 of the +// License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see . + +#endregion + +using System; +using System.Collections.ObjectModel; +using System.IO; +using System.Net.Http; +using System.Threading.Tasks; +using Pixeval.Core; +using Pixeval.Data.ViewModel; + +namespace Pixeval +{ + public static class AppContext + { + public const string AppIdentifier = "Pixeval"; + + public const string CurrentVersion = "3.0.1"; + + public const string ConfigurationFileName = "pixeval_conf.json"; + + public static readonly string ProjectFolder = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData), AppIdentifier.ToLower()); + + public static readonly string ConfFolder = ProjectFolder; + + public static readonly string SettingsFolder = ProjectFolder; + + public static readonly string ExceptionReportFolder = Path.Combine(ProjectFolder, "crash-reports"); + + public static readonly string BrowseHistoryDatabase = Path.Combine(ProjectFolder, "history.db"); + + public static string PermanentlyFolder = Path.Combine(ProjectFolder, "permanent"); + + public static readonly ObservableCollection TrendingTags = new ObservableCollection(); + + public static readonly IQualifier DefaultQualifier = new IllustrationQualifier(); + + public static readonly I18NOption[] AvailableCultures = { I18NOption.UsEnglish, I18NOption.ChineseSimplified }; + + public static int ProxyPort { get; set; } + + public static int PacPort { get; set; } + + public static async Task UpdateAvailable() + { + const string Url = "http://47.95.218.243/Pixeval/version.txt"; + var httpClient = new HttpClient(); + return await httpClient.GetStringAsync(Url) != CurrentVersion; + } + } +} \ No newline at end of file diff --git a/Pixeval/Core/AbstractPixivAsyncEnumerable.cs b/Pixeval/Core/AbstractPixivAsyncEnumerable.cs new file mode 100644 index 00000000..a11fd944 --- /dev/null +++ b/Pixeval/Core/AbstractPixivAsyncEnumerable.cs @@ -0,0 +1,67 @@ +#region Copyright (C) 2019-2020 Dylech30th. All rights reserved. + +// Pixeval - A Strong, Fast and Flexible Pixiv Client +// Copyright (C) 2019-2020 Dylech30th +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as +// published by the Free Software Foundation, either version 3 of the +// License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see . + +#endregion + +using System.Collections.Generic; +using System.Threading; + +namespace Pixeval.Core +{ + /// + /// Abstract implementation of , provides the default implementation of cancel a + /// running instance + /// + /// + public abstract class AbstractPixivAsyncEnumerable : IPixivAsyncEnumerable + { + protected bool IsCancelled { get; private set; } + + public abstract int RequestedPages { get; protected set; } + + public abstract IAsyncEnumerator GetAsyncEnumerator(CancellationToken cancellationToken = default); + + public virtual bool VerifyRationality(T item, IList collection) + { + return item != null && !collection.Contains(item); + } + + public virtual void InsertionPolicy(T item, IList collection) + { + if (item != null) + { + collection.Add(item); + } + } + + public void Cancel() + { + IsCancelled = true; + } + + public bool IsCancellationRequested() + { + return IsCancelled; + } + + public void ReportRequestedPages() + { + RequestedPages++; + } + } +} \ No newline at end of file diff --git a/Pixeval/Core/AbstractPixivAsyncEnumerator.cs b/Pixeval/Core/AbstractPixivAsyncEnumerator.cs new file mode 100644 index 00000000..095b5cee --- /dev/null +++ b/Pixeval/Core/AbstractPixivAsyncEnumerator.cs @@ -0,0 +1,50 @@ +#region Copyright (C) 2019-2020 Dylech30th. All rights reserved. + +// Pixeval - A Strong, Fast and Flexible Pixiv Client +// Copyright (C) 2019-2020 Dylech30th +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as +// published by the Free Software Foundation, either version 3 of the +// License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see . + +#endregion + +using System.Collections.Generic; +using System.Threading.Tasks; + +namespace Pixeval.Core +{ + /// + /// Provide a set of functions that support iterate an + /// + /// the correspond data type + public abstract class AbstractPixivAsyncEnumerator : IAsyncEnumerator + { + protected IPixivAsyncEnumerable Enumerable; + + protected AbstractPixivAsyncEnumerator(IPixivAsyncEnumerable enumerable) + { + Enumerable = enumerable; + } + + public ValueTask DisposeAsync() + { + return default; + } + + public abstract ValueTask MoveNextAsync(); + + public abstract T Current { get; } + + protected abstract void UpdateEnumerator(); + } +} \ No newline at end of file diff --git a/Pixeval/Core/BrowsingHistoryAccessor.cs b/Pixeval/Core/BrowsingHistoryAccessor.cs new file mode 100644 index 00000000..1b0d3b76 --- /dev/null +++ b/Pixeval/Core/BrowsingHistoryAccessor.cs @@ -0,0 +1,143 @@ +#region Copyright (C) 2019-2020 Dylech30th. All rights reserved. + +// Pixeval - A Strong, Fast and Flexible Pixiv Client +// Copyright (C) 2019-2020 Dylech30th +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as +// published by the Free Software Foundation, either version 3 of the +// License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see . + +#endregion + +using System; +using System.Collections.Generic; +using System.Collections.ObjectModel; +using System.IO; +using System.Linq; +using Pixeval.Data.ViewModel; +using SQLite; + +namespace Pixeval.Core +{ + /// + /// Manages the global browsing history which lives inside application + /// the underlying implementation is sqlite database + /// + public class BrowsingHistoryAccessor : ITimelineService, IDisposable + { + public static BrowsingHistoryAccessor GlobalLifeTimeScope; + + // current browsing histories + private readonly ObservableCollection delegation; + private readonly string path; + private readonly SQLiteConnection sqLiteConnection; + private readonly int stackLimit; + private bool writable; + + public BrowsingHistoryAccessor(int stackLimit, string path) + { + this.stackLimit = stackLimit; + this.path = path; + sqLiteConnection = new SQLiteConnection(path, SQLiteOpenFlags.Create | SQLiteOpenFlags.ReadWrite); + sqLiteConnection.CreateTable(); + delegation = new ObservableCollection(sqLiteConnection.Table()); + } + + public void Dispose() + { + sqLiteConnection?.Dispose(); + } + + public bool VerifyRationality(BrowsingHistory browsingHistory) + { + // if current browsing history list has elements + if (delegation.Any()) + { + var prev = delegation[0]; + // check if the last one in the browsing history list is the same as the one to be insert, return false if so + if (prev.Type == browsingHistory.Type && prev.BrowseObjectId == browsingHistory.BrowseObjectId) + { + return false; + } + } + + return true; + } + + public void Insert(BrowsingHistory browsingHistory) + { + // we can simply consider the browsing histories as a double-ended queue with limited capacity, we will pop the oldest one + // and insert a new one if the Deque is full + if (delegation.Count >= stackLimit) + { + delegation.Remove(delegation.Last()); + } + + delegation.Insert(0, browsingHistory); + } + + /// + /// If the database file is not present, we will urgently create one and + /// write all the browsing histories that we have into it + /// + public void EmergencyRewrite() + { + if (File.Exists(path)) + { + throw new InvalidOperationException(); + } + using var sql = new SQLiteConnection(path); + sql.CreateTable(); + sql.InsertAll(Get()); + } + + /// + /// Returns currently maintained browsing histories + /// + /// + public IEnumerable Get() + { + return delegation; + } + + /// + /// Rewrite the local database, you muse call + /// before call this method + /// + public void Rewrite() + { + if (!writable) + { + throw new InvalidOperationException(); + } + sqLiteConnection.DropTable(); + sqLiteConnection.CreateTable(); + sqLiteConnection.InsertAll(delegation); + } + + /// + /// Delete the local database + /// + public void DropDb() + { + if (File.Exists(path)) + { + File.Delete(path); + } + } + + public void SetWritable() + { + writable = true; + } + } +} \ No newline at end of file diff --git a/Pixeval/Core/CreateNewFolderForUserDownloadPathProvider.cs b/Pixeval/Core/CreateNewFolderForUserDownloadPathProvider.cs new file mode 100644 index 00000000..ebaa479d --- /dev/null +++ b/Pixeval/Core/CreateNewFolderForUserDownloadPathProvider.cs @@ -0,0 +1,51 @@ +#region Copyright (C) 2019-2020 Dylech30th. All rights reserved. + +// Pixeval - A Strong, Fast and Flexible Pixiv Client +// Copyright (C) 2019-2020 Dylech30th +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as +// published by the Free Software Foundation, either version 3 of the +// License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see . + +#endregion + +using System.IO; +using Pixeval.Objects.Primitive; +using Pixeval.Persisting; + +namespace Pixeval.Core +{ + public class CreateNewFolderForUserDownloadPathProvider : IDownloadPathProvider + { + public CreateNewFolderForUserDownloadPathProvider(string userName) + { + UserName = userName; + } + + public string UserName { get; set; } + + public string GetSpotlightPath(string title, DownloadOption option = null) + { + return option?.RootDirectory ?? Path.Combine(Settings.Global.DownloadLocation, "Spotlight", Strings.FormatPath(title)); + } + + public string GetIllustrationPath(DownloadOption option = null) + { + return option?.RootDirectory ?? Path.Combine(Settings.Global.DownloadLocation, UserName); + } + + public string GetMangaPath(string id, DownloadOption option = null) + { + return option?.RootDirectory ?? Path.Combine(Settings.Global.DownloadLocation, UserName, id); + } + } +} \ No newline at end of file diff --git a/Pixeval/Core/DefaultDownloadPathProvider.cs b/Pixeval/Core/DefaultDownloadPathProvider.cs new file mode 100644 index 00000000..7fc5e8d3 --- /dev/null +++ b/Pixeval/Core/DefaultDownloadPathProvider.cs @@ -0,0 +1,44 @@ +#region Copyright (C) 2019-2020 Dylech30th. All rights reserved. + +// Pixeval - A Strong, Fast and Flexible Pixiv Client +// Copyright (C) 2019-2020 Dylech30th +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as +// published by the Free Software Foundation, either version 3 of the +// License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see . + +#endregion + +using System.IO; +using Pixeval.Objects.Primitive; +using Pixeval.Persisting; + +namespace Pixeval.Core +{ + public class DefaultDownloadPathProvider : IDownloadPathProvider + { + public string GetSpotlightPath(string title, DownloadOption option = null) + { + return option?.RootDirectory ?? Path.Combine(Settings.Global.DownloadLocation, "Spotlight", Strings.FormatPath(title)); + } + + public string GetIllustrationPath(DownloadOption option = null) + { + return option?.RootDirectory ?? Settings.Global.DownloadLocation; + } + + public string GetMangaPath(string id, DownloadOption option = null) + { + return option?.RootDirectory ?? Path.Combine(Settings.Global.DownloadLocation, id); + } + } +} \ No newline at end of file diff --git a/Pixeval/Core/DefaultIllustrationFileNameFormatter.cs b/Pixeval/Core/DefaultIllustrationFileNameFormatter.cs new file mode 100644 index 00000000..fcddb8aa --- /dev/null +++ b/Pixeval/Core/DefaultIllustrationFileNameFormatter.cs @@ -0,0 +1,44 @@ +#region Copyright (C) 2019-2020 Dylech30th. All rights reserved. + +// Pixeval - A Strong, Fast and Flexible Pixiv Client +// Copyright (C) 2019-2020 Dylech30th +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as +// published by the Free Software Foundation, either version 3 of the +// License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see . + +#endregion + +using System.IO; +using Pixeval.Data.ViewModel; +using Pixeval.Objects.Primitive; + +namespace Pixeval.Core +{ + public class DefaultIllustrationFileNameFormatter : IIllustrationFileNameFormatter + { + public string Format(Illustration illustration) + { + return $"[{Strings.FormatPath(illustration.UserName)}]{illustration.Id}{Path.GetExtension(illustration.Origin.IsNullOrEmpty() ? illustration.Large : illustration.Origin)}"; + } + + public string FormatManga(Illustration illustration, int idx) + { + return $"[{Strings.FormatPath(illustration.UserName)}]{illustration.Id}_p{idx}{Path.GetExtension(illustration.Origin.IsNullOrEmpty() ? illustration.Large : illustration.Origin)}"; + } + + public string FormatGif(Illustration illustration) + { + return $"[{Strings.FormatPath(illustration.UserName)}]{illustration.Id}.gif"; + } + } +} \ No newline at end of file diff --git a/Pixeval/Core/EnumeratingSchedule.cs b/Pixeval/Core/EnumeratingSchedule.cs new file mode 100644 index 00000000..66c9e127 --- /dev/null +++ b/Pixeval/Core/EnumeratingSchedule.cs @@ -0,0 +1,47 @@ +#region Copyright (C) 2019-2020 Dylech30th. All rights reserved. + +// Pixeval - A Strong, Fast and Flexible Pixiv Client +// Copyright (C) 2019-2020 Dylech30th +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as +// published by the Free Software Foundation, either version 3 of the +// License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see . + +#endregion + +using System; + +namespace Pixeval.Core +{ + public class EnumeratingSchedule + { + private static object _currentItr; + + public static void StartNewInstance(IPixivAsyncEnumerable itr) + { + CancelCurrent(); + _currentItr = itr; + } + + public static IPixivAsyncEnumerable GetCurrentEnumerator() + { + return _currentItr as IPixivAsyncEnumerable; + } + + public static void CancelCurrent() + { + var iterator = _currentItr as ICancellable; + iterator?.Cancel(); + GC.Collect(); + } + } +} \ No newline at end of file diff --git a/Pixeval/Core/GalleryAsyncEnumerable.cs b/Pixeval/Core/GalleryAsyncEnumerable.cs new file mode 100644 index 00000000..19097d89 --- /dev/null +++ b/Pixeval/Core/GalleryAsyncEnumerable.cs @@ -0,0 +1,166 @@ +#region Copyright (C) 2019-2020 Dylech30th. All rights reserved. + +// Pixeval - A Strong, Fast and Flexible Pixiv Client +// Copyright (C) 2019-2020 Dylech30th +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as +// published by the Free Software Foundation, either version 3 of the +// License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see . + +#endregion + +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; +using Pixeval.Data.ViewModel; +using Pixeval.Data.Web; +using Pixeval.Data.Web.Delegation; +using Pixeval.Data.Web.Response; +using Pixeval.Objects.Exceptions; +using Pixeval.Objects.Generic; +using Pixeval.Objects.I18n; +using Pixeval.Objects.Primitive; +using Pixeval.Persisting; + +namespace Pixeval.Core +{ + public abstract class AbstractGalleryAsyncEnumerable : AbstractPixivAsyncEnumerable + { + protected abstract string Uid { get; } + + protected abstract RestrictPolicy RestrictPolicy { get; } + + public override int RequestedPages { get; protected set; } + + public override bool VerifyRationality(Illustration item, IList collection) + { + return item != null && collection.All(t => t.Id != item.Id) && PixivHelper.VerifyIllust(Settings.Global.ExcludeTag, Settings.Global.IncludeTag, Settings.Global.MinBookmark, item); + } + + public override IAsyncEnumerator GetAsyncEnumerator(CancellationToken cancellationToken = default) + { + return new GalleryAsyncEnumerator(Uid, this, RestrictPolicy); + } + + public static AbstractGalleryAsyncEnumerable Of(string uid, RestrictPolicy restrictPolicy) + { + return restrictPolicy switch + { + RestrictPolicy.Public => new PublicGalleryAsyncEnumerable(uid), + RestrictPolicy.Private => new PrivateGalleryAsyncEnumerable(uid), + _ => throw new ArgumentOutOfRangeException(nameof(restrictPolicy), restrictPolicy, null) + }; + } + + private class GalleryAsyncEnumerator : AbstractPixivAsyncEnumerator + { + private readonly RestrictPolicy restrictPolicy; + private readonly string uid; + private GalleryResponse entity; + + private IEnumerator illustrationsEnumerator; + + public GalleryAsyncEnumerator(string uid, IPixivAsyncEnumerable outerInstance, RestrictPolicy restrictPolicy) : base(outerInstance) + { + this.uid = uid; + this.restrictPolicy = restrictPolicy; + } + + public override Illustration Current => illustrationsEnumerator.Current; + + protected override void UpdateEnumerator() + { + illustrationsEnumerator = entity.Illusts.NonNull().Select(_ => _.Parse()).GetEnumerator(); + } + + public override async ValueTask MoveNextAsync() + { + if (entity == null) + { + if (await TryGetResponse(restrictPolicy switch + { + RestrictPolicy.Public => $"/v1/user/bookmarks/illust?user_id={uid}&restrict=public&filter=for_ios", + RestrictPolicy.Private => $"/v1/user/bookmarks/illust?user_id={uid}&restrict=private&filter=for_ios", + _ => throw new ArgumentOutOfRangeException() + }) is (true, var model)) + { + entity = model; + UpdateEnumerator(); + } + else + { + throw new QueryNotRespondingException(); + } + + Enumerable.ReportRequestedPages(); + } + + if (illustrationsEnumerator.MoveNext()) + { + return true; + } + + if (entity.NextUrl.IsNullOrEmpty()) + { + return false; + } + + if (await TryGetResponse(entity.NextUrl) is (true, var response)) + { + entity = response; + UpdateEnumerator(); + Enumerable.ReportRequestedPages(); + return true; + } + + return false; + } + + private static async Task> TryGetResponse(string url) + { + var result = (await HttpClientFactory.AppApiHttpClient().GetStringAsync(url)).FromJson(); + + if (result is { } response && !response.Illusts.IsNullOrEmpty()) + { + return HttpResponse.Wrap(true, response); + } + return HttpResponse.Wrap(false); + } + } + } + + public class PublicGalleryAsyncEnumerable : AbstractGalleryAsyncEnumerable + { + public PublicGalleryAsyncEnumerable(string uid) + { + Uid = uid; + } + + protected override string Uid { get; } + + protected override RestrictPolicy RestrictPolicy { get; } = RestrictPolicy.Public; + } + + public class PrivateGalleryAsyncEnumerable : AbstractGalleryAsyncEnumerable + { + public PrivateGalleryAsyncEnumerable(string uid) + { + Uid = uid; + } + + protected override string Uid { get; } + + protected override RestrictPolicy RestrictPolicy { get; } = RestrictPolicy.Private; + } +} \ No newline at end of file diff --git a/Pixeval/Core/ICancellable.cs b/Pixeval/Core/ICancellable.cs new file mode 100644 index 00000000..813d5f63 --- /dev/null +++ b/Pixeval/Core/ICancellable.cs @@ -0,0 +1,29 @@ +#region Copyright (C) 2019-2020 Dylech30th. All rights reserved. + +// Pixeval - A Strong, Fast and Flexible Pixiv Client +// Copyright (C) 2019-2020 Dylech30th +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as +// published by the Free Software Foundation, either version 3 of the +// License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see . + +#endregion + +namespace Pixeval.Core +{ + public interface ICancellable + { + void Cancel(); + + bool IsCancellationRequested(); + } +} \ No newline at end of file diff --git a/Pixeval/Core/IDownloadPathProvider.cs b/Pixeval/Core/IDownloadPathProvider.cs new file mode 100644 index 00000000..218701ed --- /dev/null +++ b/Pixeval/Core/IDownloadPathProvider.cs @@ -0,0 +1,49 @@ +#region Copyright (C) 2019-2020 Dylech30th. All rights reserved. + +// Pixeval - A Strong, Fast and Flexible Pixiv Client +// Copyright (C) 2019-2020 Dylech30th +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as +// published by the Free Software Foundation, either version 3 of the +// License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see . + +#endregion + +using Pixeval.Data.ViewModel; + +namespace Pixeval.Core +{ + public interface IIllustrationFileNameFormatter + { + string Format(Illustration illustration); + + string FormatManga(Illustration illustration, int idx); + + string FormatGif(Illustration illustration); + } + + public interface IDownloadPathProvider + { + string GetSpotlightPath(string title, DownloadOption option = null); + + string GetIllustrationPath(DownloadOption option = null); + + string GetMangaPath(string id, DownloadOption option = null); + } + + public class DownloadOption + { + public string RootDirectory { get; set; } + + public bool CreateNewWhenFromUser { get; set; } + } +} \ No newline at end of file diff --git a/Pixeval/Core/IPixivAsyncEnumerable.cs b/Pixeval/Core/IPixivAsyncEnumerable.cs new file mode 100644 index 00000000..a396aa8a --- /dev/null +++ b/Pixeval/Core/IPixivAsyncEnumerable.cs @@ -0,0 +1,35 @@ +#region Copyright (C) 2019-2020 Dylech30th. All rights reserved. + +// Pixeval - A Strong, Fast and Flexible Pixiv Client +// Copyright (C) 2019-2020 Dylech30th +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as +// published by the Free Software Foundation, either version 3 of the +// License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see . + +#endregion + +using System.Collections.Generic; + +namespace Pixeval.Core +{ + public interface IPixivAsyncEnumerable : IAsyncEnumerable, ICancellable + { + int RequestedPages { get; } + + void ReportRequestedPages(); + + void InsertionPolicy(T item, IList collection); + + bool VerifyRationality(T item, IList collection); + } +} \ No newline at end of file diff --git a/Pixeval/Core/IQualifier.cs b/Pixeval/Core/IQualifier.cs new file mode 100644 index 00000000..9f8166c3 --- /dev/null +++ b/Pixeval/Core/IQualifier.cs @@ -0,0 +1,27 @@ +#region Copyright (C) 2019-2020 Dylech30th. All rights reserved. + +// Pixeval - A Strong, Fast and Flexible Pixiv Client +// Copyright (C) 2019-2020 Dylech30th +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as +// published by the Free Software Foundation, either version 3 of the +// License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see . + +#endregion + +namespace Pixeval.Core +{ + public interface IQualifier + { + public bool Qualified(T condition, P pattern); + } +} \ No newline at end of file diff --git a/Pixeval/Core/ITimelineService.cs b/Pixeval/Core/ITimelineService.cs new file mode 100644 index 00000000..f18b353d --- /dev/null +++ b/Pixeval/Core/ITimelineService.cs @@ -0,0 +1,43 @@ +#region Copyright (C) 2019-2020 Dylech30th. All rights reserved. + +// Pixeval - A Strong, Fast and Flexible Pixiv Client +// Copyright (C) 2019-2020 Dylech30th +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as +// published by the Free Software Foundation, either version 3 of the +// License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see . + +#endregion + +using Pixeval.Data.ViewModel; + +namespace Pixeval.Core +{ + /// + /// Provides a set of functions that support a Browsing Timeline + /// + public interface ITimelineService + { + /// + /// Check if the has the rationality to be insert to timeline + /// + /// + /// + bool VerifyRationality(BrowsingHistory browsingHistory); + + /// + /// Insert a to timeline + /// + /// + void Insert(BrowsingHistory browsingHistory); + } +} \ No newline at end of file diff --git a/Pixeval/Core/IllustrationQualification.cs b/Pixeval/Core/IllustrationQualification.cs new file mode 100644 index 00000000..deb4e7b6 --- /dev/null +++ b/Pixeval/Core/IllustrationQualification.cs @@ -0,0 +1,56 @@ +#region Copyright (C) 2019-2020 Dylech30th. All rights reserved. + +// Pixeval - A Strong, Fast and Flexible Pixiv Client +// Copyright (C) 2019-2020 Dylech30th +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as +// published by the Free Software Foundation, either version 3 of the +// License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see . + +#endregion + +using System.Text.RegularExpressions; +using Pixeval.Objects.Primitive; + +namespace Pixeval.Core +{ + public class IllustrationQualification + { + public IllustrationQualification(ConditionType type, string condition) + { + Type = type; + Condition = condition; + } + + public ConditionType Type { get; set; } + + public string Condition { get; set; } + + public static IllustrationQualification Parse(string input) + { + var isResolution = Regex.IsMatch(input, "\\d+x\\d+"); + return new IllustrationQualification(input switch + { + _ when input.IsNumber() => ConditionType.Id, + _ when input.StartsWith("!") => ConditionType.ExcludeTag, + _ when !input.IsNullOrEmpty() => ConditionType.Tag, + _ when isResolution => ConditionType.Resolution, + _ => ConditionType.None + }, input); + } + } + + public enum ConditionType + { + Id, Tag, ExcludeTag, Resolution, None + } +} \ No newline at end of file diff --git a/Pixeval/Core/IllustrationQualifier.cs b/Pixeval/Core/IllustrationQualifier.cs new file mode 100644 index 00000000..15058b07 --- /dev/null +++ b/Pixeval/Core/IllustrationQualifier.cs @@ -0,0 +1,61 @@ +#region Copyright (C) 2019-2020 Dylech30th. All rights reserved. + +// Pixeval - A Strong, Fast and Flexible Pixiv Client +// Copyright (C) 2019-2020 Dylech30th +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as +// published by the Free Software Foundation, either version 3 of the +// License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see . + +#endregion + +using System.Linq; +using System.Text.RegularExpressions; +using Pixeval.Data.ViewModel; +using Pixeval.Objects.Primitive; + +namespace Pixeval.Core +{ + public class IllustrationQualifier : IQualifier + { + public bool Qualified(Illustration condition, IllustrationQualification pattern) + { + if (pattern.Type == ConditionType.Resolution) + { + const string Reg = "(?\\d+)x(?\\d+)"; + if (Regex.IsMatch(pattern.Condition, Reg)) + { + return false; + } + var illustrationMatch = Regex.Match(condition.Resolution, Reg); + var conditionMatch = Regex.Match(pattern.Condition, Reg); + if (conditionMatch.Groups["width"].Value.IsNumber() && conditionMatch.Groups["height"].Value.IsNumber()) + { + var illustrationWidth = int.Parse(illustrationMatch.Groups["width"].Value); + var illustrationHeight = int.Parse(illustrationMatch.Groups["height"].Value); + var desireWidth = int.Parse(conditionMatch.Groups["width"].Value); + var desireHeight = int.Parse(conditionMatch.Groups["height"].Value); + return desireWidth < illustrationWidth || desireHeight < illustrationHeight; + } + return false; + } + + return pattern switch + { + { Type: ConditionType.Id } => !condition.Id.Contains(pattern.Condition), + { Type: ConditionType.Tag } => !condition.Title.Contains(pattern.Condition) && !(condition.Tags != null && condition.Tags.Any(tag => tag?.Name != null && tag.Name.ToLower().Contains(pattern.Condition.ToLower()) || tag?.TranslatedName != null && tag.TranslatedName.ToLower().Contains(pattern.Condition.ToLower()))), + { Type: ConditionType.ExcludeTag } => condition.Tags != null && condition.Tags.Any(tag => tag?.Name != null && tag.Name.ToLower().Contains(pattern.Condition[1..].ToLower()) || tag?.TranslatedName != null && tag.TranslatedName.ToLower().Contains(pattern.Condition[1..].ToLower())), + _ => false + }; + } + } +} \ No newline at end of file diff --git a/Pixeval/Core/Managers.cs b/Pixeval/Core/Managers.cs new file mode 100644 index 00000000..a50adb96 --- /dev/null +++ b/Pixeval/Core/Managers.cs @@ -0,0 +1,111 @@ +#region Copyright (C) 2019-2020 Dylech30th. All rights reserved. + +// Pixeval - A Strong, Fast and Flexible Pixiv Client +// Copyright (C) 2019-2020 Dylech30th +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as +// published by the Free Software Foundation, either version 3 of the +// License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see . + +#endregion + +using System; +using System.Collections.Generic; +using System.Collections.ObjectModel; +using System.Linq; +using System.Threading.Tasks; +using System.Windows; +using Pixeval.Data.ViewModel; + +namespace Pixeval.Core +{ + public class DownloadManager + { + public static readonly ObservableCollection Downloading = new ObservableCollection(); + + public static readonly ObservableCollection Downloaded = new ObservableCollection(); + + public static void EnqueueDownloadItem(Illustration illustration, DownloadOption option = null) + { + if (Downloading.Any(i => illustration.Id == i.DownloadContent.Id)) + { + return; + } + option ??= new DownloadOption(); + + static DownloadableIllustration CreateDownloadableIllustration(Illustration downloadContent, bool isFromMange, DownloadOption option, int index = -1) + { + var filePathProvider = option.CreateNewWhenFromUser ? new CreateNewFolderForUserDownloadPathProvider(downloadContent.UserName) : (IDownloadPathProvider) new DefaultDownloadPathProvider(); + var fileNameFormatter = new DefaultIllustrationFileNameFormatter(); + var model = new DownloadableIllustration(downloadContent, fileNameFormatter, filePathProvider, isFromMange, index) { Option = option }; + model.DownloadState.ValueChanged += (sender, args) => Application.Current.Dispatcher.Invoke(() => + { + switch (args.NewValue) + { + case DownloadStateEnum.Finished: + model.Freeze(); + Downloading.Remove(model); + if (Downloaded.All(i => model.DownloadContent.GetDownloadUrl() != i.DownloadContent.GetDownloadUrl())) + { + Downloaded.Add(model); + } + break; + case DownloadStateEnum.Downloading: + Downloaded.Remove(model); + Downloading.Add(model); + break; + case var stat when stat == DownloadStateEnum.Canceled || stat == DownloadStateEnum.Queue || stat == DownloadStateEnum.Exceptional: + if (stat == DownloadStateEnum.Canceled) + { + Downloading.Remove(model); + } + break; + default: throw new ArgumentOutOfRangeException(); + } + }); + return model; + } + + if (illustration.IsManga) + { + for (var j = 0; j < illustration.MangaMetadata.Length; j++) + { + var cpy = j; + Task.Run(() => CreateDownloadableIllustration(illustration.MangaMetadata[cpy], true, option, cpy).Download()); + } + } + else + { + Task.Run(() => CreateDownloadableIllustration(illustration, false, option).Download()); + } + } + } + + public class SearchingHistoryManager + { + private static readonly ObservableCollection SearchingHistory = new ObservableCollection(); + + public static void EnqueueSearchHistory(string keyword) + { + if (SearchingHistory.Count == 4) + { + SearchingHistory.RemoveAt(SearchingHistory.Count - 1); + } + SearchingHistory.Insert(0, keyword); + } + + public static IEnumerable GetSearchingHistory() + { + return SearchingHistory; + } + } +} \ No newline at end of file diff --git a/Pixeval/Core/PixivClient.cs b/Pixeval/Core/PixivClient.cs new file mode 100644 index 00000000..d215e14e --- /dev/null +++ b/Pixeval/Core/PixivClient.cs @@ -0,0 +1,45 @@ +#region Copyright (C) 2019-2020 Dylech30th. All rights reserved. + +// Pixeval - A Strong, Fast and Flexible Pixiv Client +// Copyright (C) 2019-2020 Dylech30th +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as +// published by the Free Software Foundation, either version 3 of the +// License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see . + +#endregion + +namespace Pixeval.Core +{ + public sealed class PixivClient + { + private static volatile PixivClient _instance; + + private static readonly object Locker = new object(); + + public static PixivClient Instance + { + get + { + if (_instance == null) + { + lock (Locker) + { + _instance ??= new PixivClient(); + } + } + + return _instance; + } + } + } +} \ No newline at end of file diff --git a/Pixeval/Core/PixivClientExtension.cs b/Pixeval/Core/PixivClientExtension.cs new file mode 100644 index 00000000..f47162a5 --- /dev/null +++ b/Pixeval/Core/PixivClientExtension.cs @@ -0,0 +1,99 @@ +#region Copyright (C) 2019-2020 Dylech30th. All rights reserved. + +// Pixeval - A Strong, Fast and Flexible Pixiv Client +// Copyright (C) 2019-2020 Dylech30th +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as +// published by the Free Software Foundation, either version 3 of the +// License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see . + +#endregion + +using System; +using System.Collections.Generic; +using System.Linq; +using System.Net.Http; +using System.Text.RegularExpressions; +using System.Threading.Tasks; +using AngleSharp.Html.Parser; +using Pixeval.Data.ViewModel; +using Pixeval.Data.Web.Delegation; +using Pixeval.Data.Web.Request; + +namespace Pixeval.Core +{ + public static class PixivClientExtension + { + public static async void PostFavoriteAsync(this PixivClient _, Illustration illustration, RestrictPolicy restrictPolicy) + { + illustration.IsLiked = true; + await HttpClientFactory.AppApiService().AddBookmark(new AddBookmarkRequest { Id = illustration.Id, Restrict = restrictPolicy == RestrictPolicy.Public ? "public" : "private" }); + } + + public static async void RemoveFavoriteAsync(this PixivClient _, Illustration illustration) + { + illustration.IsLiked = false; + await HttpClientFactory.AppApiService().DeleteBookmark(new DeleteBookmarkRequest { IllustId = illustration.Id }); + } + + public static async Task> GetArticleWorks(this PixivClient _, string spotlightId) + { + var httpClient = new HttpClient(); + var html = await httpClient.GetStringAsync($"https://www.pixivision.net/en/a/{spotlightId}"); + + var doc = await new HtmlParser().ParseDocumentAsync(html); + + return doc.QuerySelectorAll(".am__body .am__work").Select(element => element.Children[1].Children[0].GetAttribute("href")).Select(url => Regex.Match(url, "https://www.pixiv.net/artworks/(?\\d+)").Groups["Id"].Value); + } + + public static async Task FollowArtist(this PixivClient _, User user, RestrictPolicy policy) + { + user.IsFollowed = true; + await HttpClientFactory.AppApiService().FollowArtist(new FollowArtistRequest { Id = user.Id, Restrict = policy == RestrictPolicy.Private ? "private" : "public" }); + } + + public static async Task UnFollowArtist(this PixivClient _, User user) + { + user.IsFollowed = false; + await HttpClientFactory.AppApiService().UnFollowArtist(new UnFollowArtistRequest { UserId = user.Id }); + } + + public static async Task> GetTrendingTags(this PixivClient _) + { + var result = await HttpClientFactory.AppApiService().GetTrendingTags(); + var list = new List(); + if (result is { } res) + { + list.AddRange(res.TrendTags.Select(tag => new TrendingTag { Tag = tag.TagStr, TranslatedName = tag.TranslatedName, Thumbnail = tag.Illust.ImageUrls.SquareMedium })); + } + return list; + } + + [Obsolete("reserved for Web API")] + public static async ValueTask ToggleWebApiR18State(this PixivClient _, bool isR18On) + { + try + { + var html = await HttpClientFactory.WebApiHttpClient().GetStringAsync("https://www.pixiv.net/setting_user.php"); + var doc = await new HtmlParser().ParseDocumentAsync(html); + + var tt = doc.QuerySelectorAll(".settingContent form input")[1].GetAttribute("value"); + await HttpClientFactory.WebApiService().ToggleR18State(new ToggleR18StateRequest { R18 = isR18On ? "show" : "hide", R18G = isR18On ? "2" : "1", Tt = tt }); + return true; + } + catch + { + return false; + } + } + } +} \ No newline at end of file diff --git a/Pixeval/Core/PixivHelper.cs b/Pixeval/Core/PixivHelper.cs new file mode 100644 index 00000000..35efb6b4 --- /dev/null +++ b/Pixeval/Core/PixivHelper.cs @@ -0,0 +1,152 @@ +#region Copyright (C) 2019-2020 Dylech30th. All rights reserved. + +// Pixeval - A Strong, Fast and Flexible Pixiv Client +// Copyright (C) 2019-2020 Dylech30th +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as +// published by the Free Software Foundation, either version 3 of the +// License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see . + +#endregion + +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; +using System.Windows; +using System.Windows.Threading; +using Pixeval.Data.ViewModel; +using Pixeval.Data.Web.Delegation; +using Pixeval.Data.Web.Response; +using Pixeval.Objects.Exceptions.Logger; +using Pixeval.Objects.Generic; +using Pixeval.Objects.Primitive; +using Refit; + +namespace Pixeval.Core +{ + public class PixivHelper + { + public static async Task IllustrationInfo(string id) + { + SingleWorkResponse.Illust response; + try + { + response = (await HttpClientFactory.AppApiService().GetSingle(id)).IllustInfo; + } + catch (ApiException e) + { + ExceptionDumper.WriteException(e); + return null; + } + catch (Exception) + { + return null; + } + + var illust = new Illustration + { + Bookmark = (int) response.TotalBookmarks, + Id = response.Id.ToString(), + IsLiked = response.IsBookmarked, + IsManga = response.PageCount != 1, + IsUgoira = response.Type == "ugoira", + Origin = response.ImageUrls.Original ?? response.MetaSinglePage.OriginalImageUrl, + Large = response.ImageUrls.Large, + Tags = response.Tags.Select(t => new Tag { Name = t.Name, TranslatedName = t.TranslatedName }), + Thumbnail = response.ImageUrls.Medium, + Title = response.Title, + UserName = response.User.Name, + UserId = response.User.Id.ToString(), + ViewCount = (int) response.TotalView, + Comments = (int) response.TotalComments, + Resolution = $"{response.Width}x{response.Height}", + PublishDate = response.CreateDate + }; + + if (illust.IsManga && response.MetaPages != null) + { + illust.MangaMetadata = response.MetaPages.Select(p => + { + var page = (Illustration) illust.Clone(); + page.Thumbnail = p.ImageUrls.Medium; + page.Origin = p.ImageUrls.Original; + page.Large = p.ImageUrls.Large; + return page; + }).ToArray(); + } + + return illust; + } + + public static async void Enumerate(IPixivAsyncEnumerable pixivIterator, IList container, int limit = -1) + { + EnumeratingSchedule.StartNewInstance(pixivIterator); + var enumerator = EnumeratingSchedule.GetCurrentEnumerator(); + + await foreach (var illust in enumerator) + { + if (enumerator.IsCancellationRequested() || limit != -1 && pixivIterator.RequestedPages > limit) + { + break; + } + if (pixivIterator.VerifyRationality(illust, container)) + { + pixivIterator.InsertionPolicy(illust, container); + } + } + } + + public static void RecordTimeline(ITimelineService service, BrowsingHistory browsingHistory) + { + if (service.VerifyRationality(browsingHistory)) + { + Application.Current.Dispatcher.Invoke(DispatcherPriority.Loaded, (Action) (() => service.Insert(browsingHistory))); + } + } + + public static void RecordTimelineInternal(BrowsingHistory browsingHistory) + { + RecordTimeline(BrowsingHistoryAccessor.GlobalLifeTimeScope, browsingHistory); + if (CheckWindowsVersion()) + { + RecordTimeline(WindowsUserActivityManager.GlobalLifeTimeScope, browsingHistory); + } + + static bool CheckWindowsVersion() + { + return Environment.OSVersion.Version >= new Version(10, 0, 17134); /* Windows 10 April 2018 Update */ + } + } + + public static bool VerifyIllust(ISet excludeTag, ISet includeTag, int minBookmark, Illustration illustration) + { + if (illustration == null) + { + return false; + } + bool excludeMatch = true, includeMatch = true; + if (!excludeTag.IsNullOrEmpty()) + { + excludeMatch = excludeTag.All(x => x.IsNullOrEmpty() || illustration.Tags.All(i => !i.Name.EqualsIgnoreCase(x))); + } + + if (!includeTag.IsNullOrEmpty()) + { + includeMatch = includeTag.All(x => x.IsNullOrEmpty() || illustration.Tags.Any(i => i.Name.EqualsIgnoreCase(x))); + } + + var minBookmarkMatch = illustration.Bookmark > minBookmark; + return excludeMatch && includeMatch && minBookmarkMatch; + } + } +} \ No newline at end of file diff --git a/Pixeval/Core/PixivIO.cs b/Pixeval/Core/PixivIO.cs new file mode 100644 index 00000000..f4acb3dc --- /dev/null +++ b/Pixeval/Core/PixivIO.cs @@ -0,0 +1,104 @@ +#region Copyright (C) 2019-2020 Dylech30th. All rights reserved. + +// Pixeval - A Strong, Fast and Flexible Pixiv Client +// Copyright (C) 2019-2020 Dylech30th +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as +// published by the Free Software Foundation, either version 3 of the +// License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see . + +#endregion + +using System; +using System.Buffers; +using System.IO; +using System.Threading; +using System.Threading.Tasks; +using System.Windows.Media.Imaging; +using Pixeval.Data.Web.Delegation; +using Pixeval.Objects.Generic; +using Pixeval.Objects.Primitive; + +namespace Pixeval.Core +{ + // ReSharper disable once InconsistentNaming + public static class PixivIO + { + public static async Task GetBytes(string url) + { + var client = HttpClientFactory.PixivImage(); + + byte[] res; + try + { + res = await client.GetByteArrayAsync(url); + } + catch + { + return null; + } + + return res; + } + + public static async Task FromUrl(string url) + { + return await FromByteArray(await GetBytes(url)); + } + + public static async Task FromByteArray(byte[] bArr) + { + if (bArr == null || bArr.Length == 0) + { + return null; + } + await using var memoryStream = new MemoryStream(bArr); + return InternalIO.CreateBitmapImageFromStream(memoryStream); + } + + public static async Task GetResizedBase64UriOfImageFromUrl(string url, string type = null) + { + return $"data:image/{type ?? url[(url.LastIndexOf('.') + 1)..]};base64,{Convert.ToBase64String(await GetBytes(url))}"; + } + + public static async Task Download(string url, IProgress progress, CancellationToken cancellationToken = default) + { + using var response = await HttpClientFactory.GetResponseHeader(HttpClientFactory.PixivImage().Apply(_ => _.Timeout = TimeSpan.FromSeconds(30)), url); + + var contentLength = response.Content.Headers.ContentLength; + if (!contentLength.HasValue) + { + return new MemoryStream(await GetBytes(url)); + } + + response.EnsureSuccessStatusCode(); + + long bytesRead, totalRead = 0L; + var byteBuffer = ArrayPool.Shared.Rent(4096); + + var memoryStream = new MemoryStream(); + await using var contentStream = await response.Content.ReadAsStreamAsync(); + while ((bytesRead = await contentStream.ReadAsync(byteBuffer, 0, byteBuffer.Length, cancellationToken)) != 0) + { + cancellationToken.ThrowIfCancellationRequested(); + totalRead += bytesRead; + await memoryStream.WriteAsync(byteBuffer, 0, (int) bytesRead, cancellationToken); + progress.Report(totalRead / (double) contentLength); + } + + cancellationToken.ThrowIfCancellationRequested(); + ArrayPool.Shared.Return(byteBuffer, true); + + return memoryStream; + } + } +} \ No newline at end of file diff --git a/Pixeval/Core/QueryAsyncEnumerable.cs b/Pixeval/Core/QueryAsyncEnumerable.cs new file mode 100644 index 00000000..cb6ac927 --- /dev/null +++ b/Pixeval/Core/QueryAsyncEnumerable.cs @@ -0,0 +1,186 @@ +#region Copyright (C) 2019-2020 Dylech30th. All rights reserved. + +// Pixeval - A Strong, Fast and Flexible Pixiv Client +// Copyright (C) 2019-2020 Dylech30th +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as +// published by the Free Software Foundation, either version 3 of the +// License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see . + +#endregion + +using System.Collections.Generic; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; +using Pixeval.Data.ViewModel; +using Pixeval.Data.Web; +using Pixeval.Data.Web.Delegation; +using Pixeval.Data.Web.Response; +using Pixeval.Objects.Exceptions; +using Pixeval.Objects.Generic; +using Pixeval.Objects.I18n; +using Pixeval.Objects.Primitive; +using Pixeval.Persisting; + +namespace Pixeval.Core +{ + public abstract class AbstractQueryAsyncEnumerable : AbstractPixivAsyncEnumerable + { + protected readonly bool IsPremium; + private readonly SearchTagMatchOption matchOption; + private readonly int start; + private readonly string tag; + + protected AbstractQueryAsyncEnumerable(string tag, SearchTagMatchOption matchOption, bool isPremium, int start = 1) + { + this.start = start < 1 ? 1 : start; + this.tag = tag; + this.matchOption = matchOption; + IsPremium = isPremium; + } + + public override int RequestedPages { get; protected set; } + + public abstract override void InsertionPolicy(Illustration item, IList collection); + + public override bool VerifyRationality(Illustration item, IList collection) + { + return item != null && collection.All(t => t.Id != item.Id) && PixivHelper.VerifyIllust(Settings.Global.ExcludeTag, Settings.Global.IncludeTag, Settings.Global.MinBookmark, item); + } + + public override IAsyncEnumerator GetAsyncEnumerator(CancellationToken cancellationToken = default) + { + return new QueryAsyncEnumerator(this, tag, matchOption, start, IsPremium); + } + + private class QueryAsyncEnumerator : AbstractPixivAsyncEnumerator + { + private readonly int current; + private readonly bool isPremium; + private readonly string keyword; + private readonly SearchTagMatchOption matchOption; + + private QueryWorksResponse entity; + + private IEnumerator illustrationsEnumerator; + + public QueryAsyncEnumerator(IPixivAsyncEnumerable enumerable, string keyword, SearchTagMatchOption matchOption, int current, bool isPremium) : base(enumerable) + { + this.keyword = keyword; + this.matchOption = matchOption; + this.current = current; + this.isPremium = isPremium; + } + + public override Illustration Current => illustrationsEnumerator.Current; + + protected override void UpdateEnumerator() + { + illustrationsEnumerator = entity.Illusts.NonNull().Select(_ => _.Parse()).GetEnumerator(); + } + + public override async ValueTask MoveNextAsync() + { + if (entity == null) + { + if (await TryGetResponse($"/v1/search/illust?search_target={matchOption.GetEnumAttribute().AliasAs}&sort={(isPremium ? "date_desc" : "popular_desc")}&word={keyword}&filter=for_android&offset={(current - 1) * 30}") is (true, var model)) + { + entity = model; + UpdateEnumerator(); + } + else + { + throw new QueryNotRespondingException(); + } + + Enumerable.ReportRequestedPages(); + } + + if (illustrationsEnumerator.MoveNext()) + { + return true; + } + + if (int.Parse(entity.NextUrl[(entity.NextUrl.LastIndexOf('=') + 1)..]) >= 5000) + { + return false; + } + + if (await TryGetResponse(entity.NextUrl) is (true, var res)) + { + entity = res; + UpdateEnumerator(); + Enumerable.ReportRequestedPages(); + return true; + } + + return false; + } + + private static async Task> TryGetResponse(string url) + { + var res = (await HttpClientFactory.AppApiHttpClient().GetStringAsync(url)).FromJson(); + if (res is { } response && !response.Illusts.IsNullOrEmpty()) + { + return HttpResponse.Wrap(true, response); + } + + return HttpResponse.Wrap(false); + } + } + } + + public class PopularityQueryAsyncEnumerable : AbstractQueryAsyncEnumerable + { + public PopularityQueryAsyncEnumerable(string tag, SearchTagMatchOption matchOption, bool isPremium, int start = 1) : base(tag, matchOption, isPremium, start) + { + } + + public override void InsertionPolicy(Illustration item, IList collection) + { + if (item != null) + { + if (IsPremium) + { + collection.Add(item); + } + else + { + collection.AddSorted(item, IllustrationPopularityComparator.Instance); + } + } + } + } + + public class PublishDateQueryAsyncEnumerable : AbstractQueryAsyncEnumerable + { + public PublishDateQueryAsyncEnumerable(string tag, SearchTagMatchOption matchOption, bool isPremium, int start = 1) : base(tag, matchOption, isPremium, start) + { + } + + public override void InsertionPolicy(Illustration item, IList collection) + { + if (item != null) + { + if (IsPremium) + { + collection.Add(item); + } + else + { + collection.AddSorted(item, IllustrationPublishDateComparator.Instance); + } + } + } + } +} \ No newline at end of file diff --git a/Pixeval/Core/RankOption.cs b/Pixeval/Core/RankOption.cs new file mode 100644 index 00000000..e22d1e0f --- /dev/null +++ b/Pixeval/Core/RankOption.cs @@ -0,0 +1,136 @@ +#region Copyright (C) 2019-2020 Dylech30th. All rights reserved. + +// Pixeval - A Strong, Fast and Flexible Pixiv Client +// Copyright (C) 2019-2020 Dylech30th +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as +// published by the Free Software Foundation, either version 3 of the +// License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see . + +#endregion + +using System; +using Pixeval.Objects.Primitive; + +namespace Pixeval.Core +{ + [AttributeUsage(AttributeTargets.Field | AttributeTargets.Property)] + public class ForR18Only : Attribute + { + } + + public enum RankOption + { + /// + /// 日榜 + /// + [EnumAlias("day")] + [EnumLocalizedName("RankOptionDay")] + Day, + + /// + /// 周榜 + /// + [EnumAlias("week")] + [EnumLocalizedName("RankOptionWeek")] + Week, + + /// + /// 月榜 + /// + [EnumAlias("month")] + [EnumLocalizedName("RankOptionMonth")] + Month, + + /// + /// 男性向日榜 + /// + [EnumAlias("day_male")] + [EnumLocalizedName("RankOptionDayMale")] + DayMale, + + /// + /// 女性向日榜 + /// + [EnumAlias("day_female")] + [EnumLocalizedName("RankOptionDayFemale")] + DayFemale, + + /// + /// 多图日榜 + /// + [EnumAlias("day_manga")] + [EnumLocalizedName("RankOptionDayManga")] + DayManga, + + /// + /// 多图周榜 + /// + [EnumAlias("week_manga")] + [EnumLocalizedName("RankOptionWeekManga")] + WeekManga, + + /// + /// 原创 + /// + [EnumAlias("week_original")] + [EnumLocalizedName("RankOptionWeekOriginal")] + WeekOriginal, + + /// + /// 新人 + /// + [EnumAlias("week_rookie")] + [EnumLocalizedName("RankOptionWeekRookie")] + WeekRookie, + + /// + /// R18日榜 + /// + [ForR18Only] + [EnumAlias("day_r18")] + [EnumLocalizedName("RankOptionDayR18")] + DayR18, + + /// + /// 男性向R18日榜 + /// + [ForR18Only] + [EnumAlias("day_male_r18")] + [EnumLocalizedName("RankOptionDayMaleR18")] + DayMaleR18, + + /// + /// 女性向R18日榜 + /// + [ForR18Only] + [EnumAlias("day_female_r18")] + [EnumLocalizedName("RankOptionDayFemaleR18")] + DayFemaleR18, + + /// + /// R18周榜 + /// + [ForR18Only] + [EnumAlias("week_r18")] + [EnumLocalizedName("RankOptionWeekR18")] + WeekR18, + + /// + /// R18G周榜 + /// + [ForR18Only] + [EnumAlias("week_r18g")] + [EnumLocalizedName("RankOptionWeekR18G")] + WeekR18G + } +} \ No newline at end of file diff --git a/Pixeval/Core/RankingAsyncEnumerable.cs b/Pixeval/Core/RankingAsyncEnumerable.cs new file mode 100644 index 00000000..fbe29516 --- /dev/null +++ b/Pixeval/Core/RankingAsyncEnumerable.cs @@ -0,0 +1,137 @@ +#region Copyright (C) 2019-2020 Dylech30th. All rights reserved. + +// Pixeval - A Strong, Fast and Flexible Pixiv Client +// Copyright (C) 2019-2020 Dylech30th +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as +// published by the Free Software Foundation, either version 3 of the +// License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see . + +#endregion + +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; +using Pixeval.Data.ViewModel; +using Pixeval.Data.Web; +using Pixeval.Data.Web.Delegation; +using Pixeval.Data.Web.Response; +using Pixeval.Objects.Exceptions; +using Pixeval.Objects.Generic; +using Pixeval.Objects.I18n; +using Pixeval.Objects.Primitive; +using Pixeval.Persisting; + +namespace Pixeval.Core +{ + public class RankingAsyncEnumerable : AbstractPixivAsyncEnumerable + { + private readonly DateTime dateTime; + private readonly RankOption rankOption; + + public RankingAsyncEnumerable(RankOption rankOption, DateTime dateTime) + { + this.rankOption = rankOption; + this.dateTime = dateTime; + } + + public override int RequestedPages { get; protected set; } + + public override bool VerifyRationality(Illustration item, IList collection) + { + return item != null && collection.All(t => t.Id != item.Id) && PixivHelper.VerifyIllust(Settings.Global.ExcludeTag, Settings.Global.IncludeTag, Settings.Global.MinBookmark, item); + } + + public override void InsertionPolicy(Illustration item, IList collection) + { + collection.Add(item); + } + + public override IAsyncEnumerator GetAsyncEnumerator(CancellationToken cancellationToken = default) + { + return new RankingAsyncEnumerator(this, rankOption, dateTime); + } + + private class RankingAsyncEnumerator : AbstractPixivAsyncEnumerator + { + private readonly string dateTimeParameter; + private readonly string rankOptionParameter; + private RankingResponse entity; + + private IEnumerator illustrationEnumerator; + + public RankingAsyncEnumerator(IPixivAsyncEnumerable enumerable, RankOption rankOption, DateTime dateTime) : base(enumerable) + { + rankOptionParameter = rankOption.GetEnumAttribute().AliasAs; + dateTimeParameter = dateTime.ToString("yyyy-MM-dd"); + } + + public override Illustration Current => illustrationEnumerator.Current; + + protected override void UpdateEnumerator() + { + illustrationEnumerator = entity.Illusts.NonNull().Select(_ => _.Parse()).GetEnumerator(); + } + + public override async ValueTask MoveNextAsync() + { + if (entity == null) + { + if (await TryGetResponse($"/v1/illust/ranking?filter=for_android&mode={rankOptionParameter}&date={dateTimeParameter}") is (true, var result)) + { + entity = result; + UpdateEnumerator(); + } + else + { + throw new QueryNotRespondingException(); + } + + Enumerable.ReportRequestedPages(); + } + + if (illustrationEnumerator.MoveNext()) + { + return true; + } + + if (entity.NextUrl.IsNullOrEmpty()) + { + return false; + } + + if (await TryGetResponse(entity.NextUrl) is (true, var model)) + { + entity = model; + UpdateEnumerator(); + Enumerable.ReportRequestedPages(); + return true; + } + + return false; + } + + private static async Task> TryGetResponse(string url) + { + var result = (await HttpClientFactory.AppApiHttpClient().GetStringAsync(url)).FromJson(); + + if (result is { } response && !response.Illusts.IsNullOrEmpty()) + { + return HttpResponse.Wrap(true, response); + } + return HttpResponse.Wrap(false); + } + } + } +} \ No newline at end of file diff --git a/Pixeval/Core/RecommendAsyncEnumerable.cs b/Pixeval/Core/RecommendAsyncEnumerable.cs new file mode 100644 index 00000000..91cc28d7 --- /dev/null +++ b/Pixeval/Core/RecommendAsyncEnumerable.cs @@ -0,0 +1,139 @@ +#region Copyright (C) 2019-2020 Dylech30th. All rights reserved. + +// Pixeval - A Strong, Fast and Flexible Pixiv Client +// Copyright (C) 2019-2020 Dylech30th +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as +// published by the Free Software Foundation, either version 3 of the +// License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see . + +#endregion + +using System.Collections.Generic; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; +using Pixeval.Data.ViewModel; +using Pixeval.Data.Web; +using Pixeval.Data.Web.Delegation; +using Pixeval.Data.Web.Response; +using Pixeval.Objects.Exceptions; +using Pixeval.Objects.Generic; +using Pixeval.Objects.I18n; +using Pixeval.Objects.Primitive; +using Pixeval.Persisting; + +namespace Pixeval.Core +{ + public abstract class AbstractRecommendAsyncEnumerable : AbstractPixivAsyncEnumerable + { + public override int RequestedPages { get; protected set; } + + public abstract override void InsertionPolicy(Illustration item, IList collection); + + public override bool VerifyRationality(Illustration item, IList collection) + { + return item != null && collection.All(t => t.Id != item.Id) && PixivHelper.VerifyIllust(Settings.Global.ExcludeTag, Settings.Global.IncludeTag, Settings.Global.MinBookmark, item); + } + + public override IAsyncEnumerator GetAsyncEnumerator(CancellationToken cancellationToken = default) + { + return new RecommendAsyncEnumerator(this); + } + + private class RecommendAsyncEnumerator : AbstractPixivAsyncEnumerator + { + private RecommendResponse entity; + + private IEnumerator illustrationEnumerator; + + public RecommendAsyncEnumerator(IPixivAsyncEnumerable enumerable) : base(enumerable) + { + } + + public override Illustration Current => illustrationEnumerator.Current; + + protected override void UpdateEnumerator() + { + illustrationEnumerator = entity.Illusts.NonNull().Select(_ => _.Parse()).GetEnumerator(); + } + + public override async ValueTask MoveNextAsync() + { + if (entity == null) + { + if (await TryGetResponse("/v1/illust/recommended") is (true, var model)) + { + entity = model; + UpdateEnumerator(); + } + else + { + throw new QueryNotRespondingException(); + } + + Enumerable.ReportRequestedPages(); + } + + if (illustrationEnumerator.MoveNext()) + { + return true; + } + + if (entity.NextUrl.IsNullOrEmpty()) + { + return false; + } + + if (await TryGetResponse(entity.NextUrl) is (true, var res)) + { + entity = res; + UpdateEnumerator(); + Enumerable.ReportRequestedPages(); + return true; + } + + return false; + } + + private static async Task> TryGetResponse(string url) + { + var res = (await HttpClientFactory.AppApiHttpClient().GetStringAsync(url)).FromJson(); + if (res is { } response && !response.Illusts.IsNullOrEmpty()) + { + return HttpResponse.Wrap(true, response); + } + + return HttpResponse.Wrap(false); + } + } + } + + public class PopularityRecommendAsyncEnumerable : AbstractRecommendAsyncEnumerable + { + public override void InsertionPolicy(Illustration item, IList collection) + { + if (item != null) + { + collection.AddSorted(item, IllustrationPopularityComparator.Instance); + } + } + } + + public class PlainRecommendAsyncEnumerable : AbstractRecommendAsyncEnumerable + { + public override void InsertionPolicy(Illustration item, IList collection) + { + collection.Add(item); + } + } +} \ No newline at end of file diff --git a/Pixeval/Core/RecommendIllustratorDeferrer.cs b/Pixeval/Core/RecommendIllustratorDeferrer.cs new file mode 100644 index 00000000..e91605d8 --- /dev/null +++ b/Pixeval/Core/RecommendIllustratorDeferrer.cs @@ -0,0 +1,71 @@ +#region Copyright (C) 2019-2020 Dylech30th. All rights reserved. + +// Pixeval - A Strong, Fast and Flexible Pixiv Client +// Copyright (C) 2019-2020 Dylech30th +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as +// published by the Free Software Foundation, either version 3 of the +// License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see . + +#endregion + +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; +using Pixeval.Data.ViewModel; +using Pixeval.Data.Web.Delegation; +using Pixeval.Data.Web.Request; + +namespace Pixeval.Core +{ + public class RecommendIllustratorDeferrer + { + public static readonly RecommendIllustratorDeferrer Instance = new RecommendIllustratorDeferrer(); + + private readonly List currentIllustrators = new List(); + + private int index; + + private int requestTimes; + + public async Task> Acquire(int count) + { + if (30 % count != 0) + { + throw new ArgumentException("count must be divisible by 30"); + } + + if (currentIllustrators.Count < index + count) + { + if (requestTimes >= 9) + { + index = 0; + requestTimes = 0; + currentIllustrators.Clear(); + } + + await Request(); + } + + var illustrators = currentIllustrators.Skip(index).Take(count); + index += count; + return illustrators; + } + + private async Task Request() + { + var newIllustrators = await HttpClientFactory.AppApiService().GetRecommendIllustrators(new RecommendIllustratorRequest { Offset = requestTimes++ * 30 }); + currentIllustrators.AddRange(newIllustrators.UserPreviews.Select(i => i.Parse())); + } + } +} \ No newline at end of file diff --git a/Pixeval/Core/RestrictPolicy.cs b/Pixeval/Core/RestrictPolicy.cs new file mode 100644 index 00000000..6d47b0a2 --- /dev/null +++ b/Pixeval/Core/RestrictPolicy.cs @@ -0,0 +1,27 @@ +#region Copyright (C) 2019-2020 Dylech30th. All rights reserved. + +// Pixeval - A Strong, Fast and Flexible Pixiv Client +// Copyright (C) 2019-2020 Dylech30th +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as +// published by the Free Software Foundation, either version 3 of the +// License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see . + +#endregion + +namespace Pixeval.Core +{ + public enum RestrictPolicy + { + Public, Private + } +} \ No newline at end of file diff --git a/Pixeval/Core/SearchTagMatchOption.cs b/Pixeval/Core/SearchTagMatchOption.cs new file mode 100644 index 00000000..3c321824 --- /dev/null +++ b/Pixeval/Core/SearchTagMatchOption.cs @@ -0,0 +1,48 @@ +#region Copyright (C) 2019-2020 Dylech30th. All rights reserved. + +// Pixeval - A Strong, Fast and Flexible Pixiv Client +// Copyright (C) 2019-2020 Dylech30th +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as +// published by the Free Software Foundation, either version 3 of the +// License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see . + +#endregion + +using Pixeval.Objects.Primitive; + +namespace Pixeval.Core +{ + public enum SearchTagMatchOption + { + /// + /// 部分一致 + /// + [EnumAlias("partial_match_for_tags")] + [EnumLocalizedName("TagMatchingPartialMatch")] + PartialMatchForTags, + + /// + /// 完全一致 + /// + [EnumAlias("exact_match_for_tags")] + [EnumLocalizedName("TagMatchingExactMatch")] + ExactMatchForTags, + + /// + /// 标题和说明文 + /// + [EnumAlias("title_and_caption")] + [EnumLocalizedName("TagMatchingTitleAndCaption")] + TitleAndCaption + } +} \ No newline at end of file diff --git a/Pixeval/Core/SpotlightQueryAsyncEnumerable.cs b/Pixeval/Core/SpotlightQueryAsyncEnumerable.cs new file mode 100644 index 00000000..57dae111 --- /dev/null +++ b/Pixeval/Core/SpotlightQueryAsyncEnumerable.cs @@ -0,0 +1,121 @@ +#region Copyright (C) 2019-2020 Dylech30th. All rights reserved. + +// Pixeval - A Strong, Fast and Flexible Pixiv Client +// Copyright (C) 2019-2020 Dylech30th +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as +// published by the Free Software Foundation, either version 3 of the +// License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see . + +#endregion + +using System.Collections.Generic; +using System.Threading; +using System.Threading.Tasks; +using Pixeval.Data.ViewModel; +using Pixeval.Data.Web; +using Pixeval.Data.Web.Delegation; +using Pixeval.Data.Web.Response; +using Pixeval.Objects.Exceptions; +using Pixeval.Objects.Generic; +using Pixeval.Objects.Primitive; + +namespace Pixeval.Core +{ + public class SpotlightQueryAsyncEnumerable : AbstractPixivAsyncEnumerable + { + private readonly int start; + + public SpotlightQueryAsyncEnumerable(int start) + { + this.start = start < 1 ? 1 : start; + } + + public override int RequestedPages { get; protected set; } + + public override IAsyncEnumerator GetAsyncEnumerator(CancellationToken cancellationToken = default) + { + return new SpotlightArticleAsyncEnumerator(this, start); + } + + private class SpotlightArticleAsyncEnumerator : AbstractPixivAsyncEnumerator + { + private int current; + + private SpotlightResponse entity; + + private IEnumerator spotlightArticleEnumerator; + + public SpotlightArticleAsyncEnumerator(IPixivAsyncEnumerable enumerable, int current) : base(enumerable) + { + this.current = current; + } + + public override SpotlightArticle Current => spotlightArticleEnumerator.Current; + + protected override void UpdateEnumerator() + { + spotlightArticleEnumerator = entity.SpotlightArticles.NonNull().GetEnumerator(); + } + + public override async ValueTask MoveNextAsync() + { + if (entity == null) + { + if (await TryGetResponse() is (true, var model)) + { + entity = model; + UpdateEnumerator(); + } + else + { + throw new QueryNotRespondingException(); + } + + Enumerable.ReportRequestedPages(); + } + + if (spotlightArticleEnumerator.MoveNext()) + { + return true; + } + + if (entity.NextUrl.IsNullOrEmpty()) + { + return false; + } + + if (await TryGetResponse() is (true, var res)) + { + entity = res; + UpdateEnumerator(); + Enumerable.ReportRequestedPages(); + return true; + } + + return false; + } + + private async Task> TryGetResponse() + { + var res = await HttpClientFactory.AppApiService().GetSpotlights(current++ * 10); + + if (res is { } response && !response.SpotlightArticles.IsNullOrEmpty()) + { + return HttpResponse.Wrap(true, response); + } + + return HttpResponse.Wrap(false); + } + } + } +} \ No newline at end of file diff --git a/Pixeval/Core/TrendsAsyncEnumerable.cs b/Pixeval/Core/TrendsAsyncEnumerable.cs new file mode 100644 index 00000000..5d4c47ec --- /dev/null +++ b/Pixeval/Core/TrendsAsyncEnumerable.cs @@ -0,0 +1,253 @@ +#region Copyright (C) 2019-2020 Dylech30th. All rights reserved. + +// Pixeval - A Strong, Fast and Flexible Pixiv Client +// Copyright (C) 2019-2020 Dylech30th +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as +// published by the Free Software Foundation, either version 3 of the +// License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see . + +#endregion + +using System; +using System.Collections.Generic; +using System.Globalization; +using System.Linq; +using System.Text.RegularExpressions; +using System.Threading; +using System.Threading.Tasks; +using Newtonsoft.Json.Linq; +using Pixeval.Data.ViewModel; +using Pixeval.Data.Web; +using Pixeval.Data.Web.Delegation; +using Pixeval.Objects.Exceptions; +using Pixeval.Objects.Generic; +using Pixeval.Objects.I18n; +using Pixeval.Objects.Primitive; + +namespace Pixeval.Core +{ + /// + /// This class is piece of shit + /// + public class TrendsAsyncEnumerable : AbstractPixivAsyncEnumerable + { + public override int RequestedPages { get; protected set; } + + public override IAsyncEnumerator GetAsyncEnumerator(CancellationToken cancellationToken = default) + { + return new TrendsAsyncEnumerator(this); + } + + private class TrendsAsyncEnumerator : AbstractPixivAsyncEnumerator + { + private TrendsRequestContext requestContext; + private IEnumerator trendsEnumerable; + private string tt; + + public TrendsAsyncEnumerator(IPixivAsyncEnumerable enumerable) : base(enumerable) + { + } + + public override Trends Current => trendsEnumerable.Current; + + public override async ValueTask MoveNextAsync() + { + if (requestContext == null) + { + if (await GetResponse(BuildRequestUrl()) is (true, var result)) + { + tt = Regex.Match(result, "tt: \"(?.*)\"").Groups["tt"].Value; + trendsEnumerable = (await ParsePreloadJsonFromHtml(result)).NonNull().GetEnumerator(); + requestContext = ExtractRequestParametersFromHtml(result); + } + else + { + throw new QueryNotRespondingException(); + } + + Enumerable.ReportRequestedPages(); + } + + if (trendsEnumerable.MoveNext()) + { + return true; + } + + if (requestContext.IsLastPage) + { + return false; + } + + if (await GetResponse(BuildRequestUrl()) is (true, var json)) + { + trendsEnumerable = (await ParseRawJson(json)).NonNull().GetEnumerator(); + requestContext = ExtractRequestParametersFromRawJson(json); + Enumerable.ReportRequestedPages(); + return true; + } + + return false; + } + + private string BuildRequestUrl() + { + return requestContext == null ? "/stacc?mode=unify" : $"/stacc/my/home/all/activity/{requestContext.Sid}/.json?mode={requestContext.Mode}&unify_token={requestContext.UnifyToken}&tt={tt}"; + } + + private static TrendsRequestContext ExtractRequestParametersFromHtml(string html) + { + var json = JObject.Parse(ExtractPreloadJsonSnippet(html)); + return new TrendsRequestContext + { + Mode = json["param"]?["mode"]?.Value(), + UnifyToken = json["param"]?["unify_token"]?.Value(), + Sid = json["next_max_sid"]?.Value().ToString(), + IsLastPage = json["is_last_page"]?.Value() == 1 + }; + } + + private static TrendsRequestContext ExtractRequestParametersFromRawJson(string json) + { + var obj = JObject.Parse(json); + return new TrendsRequestContext + { + Mode = obj["stacc"]?["param"]?["mode"]?.Value(), + UnifyToken = obj["stacc"]?["param"]?["unify_token"]?.Value(), + Sid = obj["stacc"]?["next_max_sid"]?.Value().ToString(), + IsLastPage = obj["stacc"]?["is_last_page"]?.Value() == 1 + }; + } + + private static Task> ParsePreloadJsonFromHtml(string html) + { + return ParsePreloadJson(ExtractPreloadJsonSnippet(html)); + } + + private static string ExtractPreloadJsonSnippet(string html) + { + var match = Regex.Match(html, "pixiv\\.stacc\\.env\\.preload\\.stacc \\= (?.*);"); + if (!match.Success) + { + throw new QueryNotRespondingException(); + } + return match.Groups["json"].Value; + } + + private static Task> ParseRawJson(string json) + { + var stacc = JObject.Parse(json)["stacc"]?.ToString(); + return ParsePreloadJson(stacc); + } + + private static async Task> ParsePreloadJson(string json) + { + var tasks = new List>(); + var stacc = JObject.Parse(json); + var status = stacc["status"]; + var timeline = stacc["timeline"]; + var user = stacc["user"]; + var illust = stacc["illust"]; + foreach (var timelineChild in timeline!) + { + var task = Task.Run(() => + { + var timelineProp = timelineChild.First; + var statusObj = status?.FirstOrDefault(sChild => sChild.First?["id"]?.Value() == timelineProp?["id"]?.Value()); + if (statusObj?.First == null) + { + return null; + } + var statusObjProp = statusObj.First; + var trendsObj = new Trends + { + PostDate = DateTime.Parse(statusObjProp?["post_date"]?.Value()!, CultureInfo.CurrentCulture), + PostUserId = statusObjProp["post_user"]?["id"]?.Value(), + TrendObjectId = statusObjProp["type"]?.Value() switch + { + var type when type == "add_illust" || type == "add_bookmark" => statusObjProp["ref_illust"]?["id"]?.Value(), + "add_favorite" => statusObjProp["ref_user"]?["id"]?.Value(), + _ => null + } + }; + var matchingPostUser = user?.FirstOrDefault(uChild => uChild.First?["id"]?.Value() == trendsObj.PostUserId); + if (matchingPostUser != null) + { + trendsObj.PostUserThumbnail = matchingPostUser.First?["profile_image"]?.First?.First?["url"]?["m"]?.Value(); + trendsObj.PostUserName = matchingPostUser.First?["name"]?.Value(); + } + else + { + return null; + } + + trendsObj.Type = statusObjProp["type"]?.Value() switch + { + "add_illust" => TrendType.AddIllust, + "add_bookmark" => TrendType.AddBookmark, + "add_favorite" => TrendType.AddFavorite, + _ => (TrendType) (-1) + }; + trendsObj.TrendObjectThumbnail = trendsObj.Type switch + { + var type when type == TrendType.AddBookmark || type == TrendType.AddIllust => illust?.FirstOrDefault(iChild => iChild.First?["id"]?.Value() == trendsObj.TrendObjectId)?.First?["url"]?["m"]?.Value(), + TrendType.AddFavorite => user.FirstOrDefault(uChild => uChild.First?["id"]?.Value() == trendsObj.TrendObjectId)?.First?["profile_image"]?.First?.First?["url"]?["s"]?.Value(), + (TrendType) (-1) => null, + _ => throw new ArgumentOutOfRangeException() + }; + if (trendsObj.Type != TrendType.AddFavorite) + { + var illustration = illust.FirstOrDefault(iChild => iChild.First?["id"]?.Value() == trendsObj.TrendObjectId); + if (illustration != null) + { + trendsObj.ByName = user.FirstOrDefault(uChild => uChild.First?["id"]?.Value() == illustration.First?["post_user"]?["id"]?.Value())?.First["name"].Value(); + trendsObj.TrendObjName = illustration.First["title"].Value(); + } + } + else + { + trendsObj.TrendObjName = user.FirstOrDefault(uChild => uChild.First?["id"]?.Value() == trendsObj.TrendObjectId)?.First["name"].Value(); + trendsObj.IsReferToUser = true; + } + + return trendsObj; + }); + tasks.Add(task); + } + + return await Task.WhenAll(tasks); + } + + protected override void UpdateEnumerator() + { + throw new NotImplementedException(); + } + + private static async Task> GetResponse(string url) + { + var result = await HttpClientFactory.WebApiHttpClient().GetStringAsync(url); + return !result.IsNullOrEmpty() ? HttpResponse.Wrap(true, result) : HttpResponse.Wrap(false); + } + } + + private class TrendsRequestContext + { + public string UnifyToken { get; set; } + + public string Sid { get; set; } + + public string Mode { get; set; } + + public bool IsLastPage { get; set; } + } + } +} \ No newline at end of file diff --git a/Pixeval/Core/UploadAsyncEnumerable.cs b/Pixeval/Core/UploadAsyncEnumerable.cs new file mode 100644 index 00000000..2ad5c846 --- /dev/null +++ b/Pixeval/Core/UploadAsyncEnumerable.cs @@ -0,0 +1,136 @@ +#region Copyright (C) 2019-2020 Dylech30th. All rights reserved. + +// Pixeval - A Strong, Fast and Flexible Pixiv Client +// Copyright (C) 2019-2020 Dylech30th +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as +// published by the Free Software Foundation, either version 3 of the +// License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see . + +#endregion + +using System.Collections.Generic; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; +using Pixeval.Data.ViewModel; +using Pixeval.Data.Web; +using Pixeval.Data.Web.Delegation; +using Pixeval.Data.Web.Response; +using Pixeval.Objects.Exceptions; +using Pixeval.Objects.Generic; +using Pixeval.Objects.I18n; +using Pixeval.Objects.Primitive; +using Pixeval.Persisting; + +namespace Pixeval.Core +{ + public class UploadAsyncEnumerable : AbstractPixivAsyncEnumerable + { + private readonly string uid; + + public UploadAsyncEnumerable(string uid) + { + this.uid = uid; + } + + public override int RequestedPages { get; protected set; } + + public override void InsertionPolicy(Illustration item, IList collection) + { + if (item != null) + { + collection.AddSorted(item, IllustrationPublishDateComparator.Instance); + } + } + + public override bool VerifyRationality(Illustration item, IList collection) + { + return item != null && collection.All(t => t.Id != item.Id) && PixivHelper.VerifyIllust(Settings.Global.ExcludeTag, Settings.Global.IncludeTag, Settings.Global.MinBookmark, item); + } + + public override IAsyncEnumerator GetAsyncEnumerator(CancellationToken cancellationToken = default) + { + return new UploadAsyncEnumerator(this, uid); + } + + private class UploadAsyncEnumerator : AbstractPixivAsyncEnumerator + { + private readonly string uid; + + private UploadResponse entity; + + private IEnumerator illustrationEnumerator; + + public UploadAsyncEnumerator(IPixivAsyncEnumerable enumerable, string uid) : base(enumerable) + { + this.uid = uid; + } + + public override Illustration Current => illustrationEnumerator.Current; + + protected override void UpdateEnumerator() + { + illustrationEnumerator = entity.Illusts.NonNull().Select(_ => _.Parse()).GetEnumerator(); + } + + public override async ValueTask MoveNextAsync() + { + if (entity == null) + { + if (await TryGetResponse($"/v1/user/illusts?user_id={uid}&filter=for_android&type=illust") is (true, var model)) + { + entity = model; + UpdateEnumerator(); + } + else + { + throw new QueryNotRespondingException(); + } + + Enumerable.ReportRequestedPages(); + } + + if (illustrationEnumerator.MoveNext()) + { + return true; + } + + if (entity.NextUrl.IsNullOrEmpty()) + { + return false; + } + + if (await TryGetResponse(entity.NextUrl) is (true, var res)) + { + entity = res; + UpdateEnumerator(); + Enumerable.ReportRequestedPages(); + return true; + } + + return false; + } + + private static async Task> TryGetResponse(string url) + { + var res = (await HttpClientFactory.AppApiHttpClient().GetStringAsync(url)).FromJson(); + if (res is { } response && !response.Illusts.IsNullOrEmpty()) + { + return HttpResponse.Wrap(true, response); + } + + return HttpResponse.Wrap(false); + } + } + } +} \ No newline at end of file diff --git a/Pixeval/Core/UserFollowingAsyncEnumerable.cs b/Pixeval/Core/UserFollowingAsyncEnumerable.cs new file mode 100644 index 00000000..cd378290 --- /dev/null +++ b/Pixeval/Core/UserFollowingAsyncEnumerable.cs @@ -0,0 +1,166 @@ +#region Copyright (C) 2019-2020 Dylech30th. All rights reserved. + +// Pixeval - A Strong, Fast and Flexible Pixiv Client +// Copyright (C) 2019-2020 Dylech30th +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as +// published by the Free Software Foundation, either version 3 of the +// License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see . + +#endregion + +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; +using Pixeval.Data.ViewModel; +using Pixeval.Data.Web; +using Pixeval.Data.Web.Delegation; +using Pixeval.Data.Web.Response; +using Pixeval.Objects.Exceptions; +using Pixeval.Objects.Generic; +using Pixeval.Objects.Primitive; + +namespace Pixeval.Core +{ + public abstract class AbstractUserFollowingAsyncEnumerable : AbstractPixivAsyncEnumerable + { + protected abstract string Uid { get; } + + protected abstract RestrictPolicy RestrictPolicy { get; } + + public override int RequestedPages { get; protected set; } + + public override IAsyncEnumerator GetAsyncEnumerator(CancellationToken cancellationToken = default) + { + return new UserFollowingAsyncEnumerator(this, Uid, RestrictPolicy); + } + + public static AbstractUserFollowingAsyncEnumerable Of(string uid, RestrictPolicy restrictPolicy) + { + return restrictPolicy switch + { + RestrictPolicy.Public => new PublicUserFollowingAsyncEnumerable(uid), + RestrictPolicy.Private => new PrivateUserFollowingAsyncEnumerable(uid), + _ => throw new ArgumentOutOfRangeException(nameof(restrictPolicy), restrictPolicy, null) + }; + } + + private class UserFollowingAsyncEnumerator : AbstractPixivAsyncEnumerator + { + private readonly RestrictPolicy restrictPolicy; + private readonly string userId; + + private FollowingResponse entity; + + private IEnumerator followerEnumerator; + + public UserFollowingAsyncEnumerator(IPixivAsyncEnumerable enumerable, string userId, RestrictPolicy restrictPolicy) : base(enumerable) + { + this.userId = userId; + this.restrictPolicy = restrictPolicy; + } + + public override User Current => followerEnumerator.Current; + + protected override void UpdateEnumerator() + { + followerEnumerator = entity.UserPreviews.NonNull().Select(u => new User + { + Thumbnails = u.Illusts.NonNull().Select(_ => _.ImageUrls.SquareMedium).ToArray(), + Id = u.User.Id.ToString(), + Name = u.User.Name, + Avatar = u.User.ProfileImageUrls.Medium + }).GetEnumerator(); + } + + public override async ValueTask MoveNextAsync() + { + if (entity == null) + { + if (await TryGetResponse(restrictPolicy switch + { + RestrictPolicy.Public => $"/v1/user/following?user_id={userId}&restrict=public", + RestrictPolicy.Private => $"/v1/user/following?user_id={userId}&restrict=private", + _ => throw new ArgumentOutOfRangeException() + }) is (true, var model)) + { + entity = model; + UpdateEnumerator(); + } + else + { + throw new QueryNotRespondingException(); + } + + Enumerable.ReportRequestedPages(); + } + + if (followerEnumerator.MoveNext()) + { + return true; + } + + if (entity.NextUrl.IsNullOrEmpty()) + { + return false; + } + + if (await TryGetResponse(entity.NextUrl) is (true, var res)) + { + entity = res; + UpdateEnumerator(); + Enumerable.ReportRequestedPages(); + return true; + } + + return false; + } + + private static async Task> TryGetResponse(string url) + { + var res = (await HttpClientFactory.AppApiHttpClient().GetStringAsync(url)).FromJson(); + if (res is { } response && !response.UserPreviews.IsNullOrEmpty()) + { + return HttpResponse.Wrap(true, response); + } + + return HttpResponse.Wrap(false); + } + } + } + + public class PublicUserFollowingAsyncEnumerable : AbstractUserFollowingAsyncEnumerable + { + public PublicUserFollowingAsyncEnumerable(string uid) + { + Uid = uid; + } + + protected override string Uid { get; } + + protected override RestrictPolicy RestrictPolicy { get; } = RestrictPolicy.Public; + } + + public class PrivateUserFollowingAsyncEnumerable : AbstractUserFollowingAsyncEnumerable + { + public PrivateUserFollowingAsyncEnumerable(string uid) + { + Uid = uid; + } + + protected override string Uid { get; } + + protected override RestrictPolicy RestrictPolicy { get; } = RestrictPolicy.Private; + } +} \ No newline at end of file diff --git a/Pixeval/Core/UserPreviewAsyncEnumerable.cs b/Pixeval/Core/UserPreviewAsyncEnumerable.cs new file mode 100644 index 00000000..41d85849 --- /dev/null +++ b/Pixeval/Core/UserPreviewAsyncEnumerable.cs @@ -0,0 +1,126 @@ +#region Copyright (C) 2019-2020 Dylech30th. All rights reserved. + +// Pixeval - A Strong, Fast and Flexible Pixiv Client +// Copyright (C) 2019-2020 Dylech30th +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as +// published by the Free Software Foundation, either version 3 of the +// License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see . + +#endregion + +using System.Collections.Generic; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; +using Pixeval.Data.ViewModel; +using Pixeval.Data.Web; +using Pixeval.Data.Web.Delegation; +using Pixeval.Data.Web.Response; +using Pixeval.Objects.Exceptions; +using Pixeval.Objects.Generic; +using Pixeval.Objects.Primitive; + +namespace Pixeval.Core +{ + public class UserPreviewAsyncEnumerable : AbstractPixivAsyncEnumerable + { + private readonly string keyword; + + public UserPreviewAsyncEnumerable(string keyword) + { + this.keyword = keyword; + } + + public override int RequestedPages { get; protected set; } + + public override IAsyncEnumerator GetAsyncEnumerator(CancellationToken cancellationToken = default) + { + return new UserPreviewAsyncEnumerator(this, keyword); + } + + private class UserPreviewAsyncEnumerator : AbstractPixivAsyncEnumerator + { + private readonly string keyword; + private UserNavResponse entity; + + private IEnumerator userPreviewEnumerator; + + public UserPreviewAsyncEnumerator(IPixivAsyncEnumerable enumerable, string keyword) : base(enumerable) + { + this.keyword = keyword; + } + + public override User Current => userPreviewEnumerator.Current; + + protected override void UpdateEnumerator() + { + userPreviewEnumerator = entity.UserPreviews.NonNull().Select(u => new User + { + Avatar = u.User.ProfileImageUrls.Medium, + Thumbnails = u.Illusts.NonNull().Select(_ => _.ImageUrl.SquareMedium).ToArray(), + Id = u.User.Id.ToString(), + Name = u.User.Name + }).GetEnumerator(); + } + + public override async ValueTask MoveNextAsync() + { + if (entity == null) + { + if (await TryGetResponse($"https://app-api.pixiv.net/v1/search/user?filter=for_android&word={keyword}") is (true, var model)) + { + entity = model; + UpdateEnumerator(); + } + else + { + throw new QueryNotRespondingException(); + } + + Enumerable.ReportRequestedPages(); + } + + if (userPreviewEnumerator.MoveNext()) + { + return true; + } + + if (entity.NextUrl.IsNullOrEmpty()) + { + return false; + } + + if (await TryGetResponse(entity.NextUrl) is (true, var res)) + { + entity = res; + UpdateEnumerator(); + Enumerable.ReportRequestedPages(); + return true; + } + + return false; + } + + private static async Task> TryGetResponse(string url) + { + var res = (await HttpClientFactory.AppApiHttpClient().GetStringAsync(url)).FromJson(); + if (res is { } response && !response.UserPreviews.IsNullOrEmpty()) + { + return HttpResponse.Wrap(true, response); + } + + return HttpResponse.Wrap(false); + } + } + } +} \ No newline at end of file diff --git a/Pixeval/Core/UserUpdateAsyncEnumerable.cs b/Pixeval/Core/UserUpdateAsyncEnumerable.cs new file mode 100644 index 00000000..bcb1d14a --- /dev/null +++ b/Pixeval/Core/UserUpdateAsyncEnumerable.cs @@ -0,0 +1,118 @@ +#region Copyright (C) 2019-2020 Dylech30th. All rights reserved. + +// Pixeval - A Strong, Fast and Flexible Pixiv Client +// Copyright (C) 2019-2020 Dylech30th +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as +// published by the Free Software Foundation, either version 3 of the +// License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see . + +#endregion + +using System.Collections.Generic; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; +using Pixeval.Data.ViewModel; +using Pixeval.Data.Web; +using Pixeval.Data.Web.Delegation; +using Pixeval.Data.Web.Response; +using Pixeval.Objects.Exceptions; +using Pixeval.Objects.Generic; +using Pixeval.Objects.I18n; +using Pixeval.Objects.Primitive; +using Pixeval.Persisting; + +namespace Pixeval.Core +{ + public class UserUpdateAsyncEnumerable : AbstractPixivAsyncEnumerable + { + public override int RequestedPages { get; protected set; } + + public override IAsyncEnumerator GetAsyncEnumerator(CancellationToken cancellationToken = default) + { + return new UserUpdateAsyncEnumerator(this); + } + + public override bool VerifyRationality(Illustration item, IList collection) + { + return item != null && collection.All(t => t.Id != item.Id) && PixivHelper.VerifyIllust(Settings.Global.ExcludeTag, Settings.Global.IncludeTag, Settings.Global.MinBookmark, item); + } + + private class UserUpdateAsyncEnumerator : AbstractPixivAsyncEnumerator + { + private UserUpdateResponse entity; + + private IEnumerator illustrationEnumerator; + + public UserUpdateAsyncEnumerator(IPixivAsyncEnumerable enumerable) : base(enumerable) + { + } + + public override Illustration Current => illustrationEnumerator.Current; + + protected override void UpdateEnumerator() + { + illustrationEnumerator = entity.Illusts.NonNull().Select(_ => _.Parse()).GetEnumerator(); + } + + public override async ValueTask MoveNextAsync() + { + if (entity == null) + { + if (await TryGetResponse("https://app-api.pixiv.net/v2/illust/follow?restrict=public") is (true, var model)) + { + entity = model; + UpdateEnumerator(); + } + else + { + throw new QueryNotRespondingException(); + } + + Enumerable.ReportRequestedPages(); + } + + if (illustrationEnumerator.MoveNext()) + { + return true; + } + + if (entity.NextUrl.IsNullOrEmpty()) + { + return false; + } + + if (await TryGetResponse(entity.NextUrl) is (true, var res)) + { + entity = res; + UpdateEnumerator(); + Enumerable.ReportRequestedPages(); + return true; + } + + return false; + } + + private static async Task> TryGetResponse(string url) + { + var res = (await HttpClientFactory.AppApiHttpClient().GetStringAsync(url)).FromJson(); + if (res is { } response && !response.Illusts.IsNullOrEmpty()) + { + return HttpResponse.Wrap(true, response); + } + + return HttpResponse.Wrap(false); + } + } + } +} \ No newline at end of file diff --git a/Pixeval/Core/WindowsUserActivityManager.cs b/Pixeval/Core/WindowsUserActivityManager.cs new file mode 100644 index 00000000..8be10354 --- /dev/null +++ b/Pixeval/Core/WindowsUserActivityManager.cs @@ -0,0 +1,137 @@ +#region Copyright (C) 2019-2020 Dylech30th. All rights reserved. + +// Pixeval - A Strong, Fast and Flexible Pixiv Client +// Copyright (C) 2019-2020 Dylech30th +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as +// published by the Free Software Foundation, either version 3 of the +// License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see . + +#endregion + +using System; +using System.Threading.Tasks; +using Windows.ApplicationModel.UserActivities; +using Windows.UI.Shell; +using AdaptiveCards; +using Newtonsoft.Json; +using Pixeval.Data.ViewModel; +using Pixeval.Objects.Generic; + +namespace Pixeval.Core +{ + /// + /// Provides a set of functions to create a Windows 10 Timeline Activity, + /// for more information and underlying implementation, see + /// + /// + public class WindowsUserActivityManager : ITimelineService + { + public static readonly WindowsUserActivityManager GlobalLifeTimeScope = new WindowsUserActivityManager(); + private readonly Uri iconUri = new Uri("http://qa23pqcql.bkt.clouddn.com/pxlogo.ico"); + private UserActivitySession userActivitySession; + + public bool VerifyRationality(BrowsingHistory browsingHistory) + { + return true; + } + + public async void Insert(BrowsingHistory browsingHistory) + { + var userActivityChannel = UserActivityChannel.GetDefault(); + var model = await GetPixevalTimelineModel(browsingHistory); + var userActivity = await userActivityChannel.GetOrCreateUserActivityAsync($"Pixeval-{model.Id}-{DateTime.Now:s}"); + userActivity.VisualElements.DisplayText = model.Title; + userActivity.VisualElements.Content = AdaptiveCardBuilder.CreateAdaptiveCardFromJson(BuildAdaptiveCard(model)); + userActivity.VisualElements.Attribution = new UserActivityAttribution(iconUri); + userActivity.VisualElements.AttributionDisplayText = "Pixeval"; + userActivity.ActivationUri = new Uri(browsingHistory.Type switch + { + "illust" => $"pixeval://www.pixiv.net/artworks/{model.Id}", + "user" => $"pixeval://www.pixiv.net/users/{model.Id}", + "spotlight" => $"pixeval://www.pixivision.net/en/a/{model.Id}", + _ => throw new ArgumentException(nameof(browsingHistory.Type)) + }); + await userActivity.SaveAsync(); + userActivitySession?.Dispose(); + userActivitySession = userActivity.CreateSession(); + } + + private static async Task GetPixevalTimelineModel(BrowsingHistory history) + { + var p = new PixevalTimelineModel { Background = await PixivIO.GetResizedBase64UriOfImageFromUrl(history.BrowseObjectThumbnail), Id = history.BrowseObjectId, Title = history.BrowseObjectState }; + switch (history.Type) + { + case "illust": + p.Author = history.IllustratorName; + return p; + case "user": + case "spotlight": return p; + } + + throw new ArgumentException(nameof(history.Type)); + } + + private static string BuildAdaptiveCard(PixevalTimelineModel pixevalTimelineModel) + { + var card = new StringifyBackgroundAdaptiveCard("1.0") { StringifyUrl = pixevalTimelineModel.Background }; + card.Body.Add(new AdaptiveTextBlock + { + Text = pixevalTimelineModel.Title, + Weight = AdaptiveTextWeight.Bolder, + Wrap = true, + Size = AdaptiveTextSize.Large, + MaxLines = 3 + }); + if (!pixevalTimelineModel.Author.IsNullOrEmpty()) + { + card.Body.Add(new AdaptiveTextBlock + { + Text = pixevalTimelineModel.Author, + Weight = AdaptiveTextWeight.Bolder, + Wrap = true, + Size = AdaptiveTextSize.Small, + MaxLines = 3, + Spacing = AdaptiveSpacing.Small + }); + } + + return card.ToJson(); + } + + private class PixevalTimelineModel + { + public string Id { get; set; } + + public string Title { get; set; } + + public string Author { get; set; } + + public string Background { get; set; } + } + + // https://stackoverflow.com/questions/55663963/adaptive-cards-serving-images-in-bytes + private class StringifyBackgroundAdaptiveCard : AdaptiveCard + { + public StringifyBackgroundAdaptiveCard(AdaptiveSchemaVersion schemaVersion) : base(schemaVersion) + { + } + + public StringifyBackgroundAdaptiveCard(string schemaVersion) : base(schemaVersion) + { + } + + [JsonProperty("backgroundImage")] + public string StringifyUrl { get; set; } + } + } +} \ No newline at end of file diff --git a/Pixeval/Data/IParser.cs b/Pixeval/Data/IParser.cs new file mode 100644 index 00000000..62834e61 --- /dev/null +++ b/Pixeval/Data/IParser.cs @@ -0,0 +1,27 @@ +#region Copyright (C) 2019-2020 Dylech30th. All rights reserved. + +// Pixeval - A Strong, Fast and Flexible Pixiv Client +// Copyright (C) 2019-2020 Dylech30th +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as +// published by the Free Software Foundation, either version 3 of the +// License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see . + +#endregion + +namespace Pixeval.Data +{ + public interface IParser + { + T Parse(); + } +} \ No newline at end of file diff --git a/Pixeval/Data/ViewModel/AutoCompletion.cs b/Pixeval/Data/ViewModel/AutoCompletion.cs new file mode 100644 index 00000000..77381f04 --- /dev/null +++ b/Pixeval/Data/ViewModel/AutoCompletion.cs @@ -0,0 +1,32 @@ +#region Copyright (C) 2019-2020 Dylech30th. All rights reserved. + +// Pixeval - A Strong, Fast and Flexible Pixiv Client +// Copyright (C) 2019-2020 Dylech30th +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as +// published by the Free Software Foundation, either version 3 of the +// License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see . + +#endregion + +using PropertyChanged; + +namespace Pixeval.Data.ViewModel +{ + [AddINotifyPropertyChangedInterface] + public class AutoCompletion + { + public string Tag { get; set; } + + public string TranslatedName { get; set; } + } +} \ No newline at end of file diff --git a/Pixeval/Data/ViewModel/BrowsingHistory.cs b/Pixeval/Data/ViewModel/BrowsingHistory.cs new file mode 100644 index 00000000..d7380846 --- /dev/null +++ b/Pixeval/Data/ViewModel/BrowsingHistory.cs @@ -0,0 +1,58 @@ +#region Copyright (C) 2019-2020 Dylech30th. All rights reserved. + +// Pixeval - A Strong, Fast and Flexible Pixiv Client +// Copyright (C) 2019-2020 Dylech30th +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as +// published by the Free Software Foundation, either version 3 of the +// License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see . + +#endregion + +using PropertyChanged; +using SQLite; + +namespace Pixeval.Data.ViewModel +{ + [AddINotifyPropertyChangedInterface] + public class BrowsingHistory + { + [PrimaryKey] + [AutoIncrement] + public int Index { get; set; } + + /// + /// 提供当前视图,默认的值是名称/标题 + /// + public string BrowseObjectState { get; set; } + + public string BrowseObjectThumbnail { get; set; } + + /// + /// 有效仅当此属性有效 + /// + public string IllustratorName { get; set; } + + public bool IsReferToUser { get; set; } + + public bool IsReferToIllust { get; set; } + + public bool IsReferToSpotlight { get; set; } + + /// + /// 提供当前的id的视图,默认的值是作品ID/用户ID/特辑ID + /// + public string BrowseObjectId { get; set; } + + public string Type { get; set; } + } +} \ No newline at end of file diff --git a/Pixeval/Data/ViewModel/ConditionString.cs b/Pixeval/Data/ViewModel/ConditionString.cs new file mode 100644 index 00000000..e37b6848 --- /dev/null +++ b/Pixeval/Data/ViewModel/ConditionString.cs @@ -0,0 +1,33 @@ +#region Copyright (C) 2019-2020 Dylech30th. All rights reserved. + +// Pixeval - A Strong, Fast and Flexible Pixiv Client +// Copyright (C) 2019-2020 Dylech30th +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as +// published by the Free Software Foundation, either version 3 of the +// License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see . + +#endregion + +using PropertyChanged; + +namespace Pixeval.Data.ViewModel +{ + [AddINotifyPropertyChangedInterface] + public class ConditionString + { + [DoNotNotify] + public static ConditionString Shared = new ConditionString(); + + public string Condition { get; set; } + } +} \ No newline at end of file diff --git a/Pixeval/Data/ViewModel/DownloadableIllustration.cs b/Pixeval/Data/ViewModel/DownloadableIllustration.cs new file mode 100644 index 00000000..0a7c0581 --- /dev/null +++ b/Pixeval/Data/ViewModel/DownloadableIllustration.cs @@ -0,0 +1,227 @@ +#region Copyright (C) 2019-2020 Dylech30th. All rights reserved. + +// Pixeval - A Strong, Fast and Flexible Pixiv Client +// Copyright (C) 2019-2020 Dylech30th +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as +// published by the Free Software Foundation, either version 3 of the +// License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see . + +#endregion + +using System; +using System.IO; +using System.Linq; +using System.Text.RegularExpressions; +using System.Threading; +using System.Threading.Tasks; +using Pixeval.Core; +using Pixeval.Data.Web.Delegation; +using Pixeval.Objects.Generic; +using Pixeval.Objects.Primitive; +using PropertyChanged; + +namespace Pixeval.Data.ViewModel +{ + [AddINotifyPropertyChangedInterface] + public class DownloadableIllustration + { + [DoNotNotify] + private CancellationTokenSource cancellationTokenSource = new CancellationTokenSource(); + + private bool modifiable = true; + + private bool retried; + + public DownloadableIllustration(Illustration downloadContent, IIllustrationFileNameFormatter fileNameFormatter, IDownloadPathProvider downloadPathProvider, bool isFromManga, int mangaIndex = -1) + { + DownloadContent = downloadContent; + FileNameFormatter = fileNameFormatter; + DownloadPathProvider = downloadPathProvider; + IsFromManga = isFromManga; + MangaIndex = mangaIndex; + } + + public Illustration DownloadContent { get; set; } + + public IIllustrationFileNameFormatter FileNameFormatter { get; set; } + + public IDownloadPathProvider DownloadPathProvider { get; set; } + + public bool IsFromManga { get; set; } + + public int MangaIndex { get; set; } + + public bool DownloadFailed { get; set; } + + public double Progress { get; set; } + + public string ReasonPhase { get; set; } + + public Observable DownloadState { get; set; } = new Observable(DownloadStateEnum.Queue); + + public DownloadOption Option { get; set; } + + public string GetPath() + { + if (DownloadContent.IsUgoira) + { + return Path.Combine(Directory.CreateDirectory(DownloadPathProvider.GetIllustrationPath(Option)).FullName, FileNameFormatter.FormatGif(DownloadContent)); + } + if (DownloadContent.FromSpotlight) + { + return IsFromManga ? Path.Combine(Directory.CreateDirectory(DownloadPathProvider.GetSpotlightPath(DownloadContent.SpotlightTitle, Option)).FullName, DownloadContent.Id, FileNameFormatter.FormatManga(DownloadContent, MangaIndex)) : Path.Combine(Directory.CreateDirectory(DownloadPathProvider.GetSpotlightPath(DownloadContent.SpotlightTitle, Option)).FullName, FileNameFormatter.Format(DownloadContent)); + } + return IsFromManga ? Path.Combine(Directory.CreateDirectory(DownloadPathProvider.GetMangaPath(DownloadContent.Id, Option)).FullName, FileNameFormatter.FormatManga(DownloadContent, MangaIndex)) : Path.Combine(Directory.CreateDirectory(DownloadPathProvider.GetIllustrationPath(Option)).FullName, FileNameFormatter.Format(DownloadContent)); + } + + + public void Freeze() + { + modifiable = false; + } + + public void Cancel() + { + if (modifiable) + { + cancellationTokenSource.Cancel(); + cancellationTokenSource = new CancellationTokenSource(); + Progress = 0; + ReasonPhase = null; + DownloadFailed = false; + DownloadState.Value = DownloadStateEnum.Canceled; + } + } + + public void Restart() + { + if (modifiable) + { + cancellationTokenSource.Cancel(); + cancellationTokenSource = new CancellationTokenSource(); + Progress = 0; + ReasonPhase = null; + DownloadFailed = false; + Download(); + } + } + + public async void Download() + { + if (!modifiable) + { + return; + } + + DownloadState.Value = DownloadStateEnum.Downloading; + var downloadPath = GetPath(); + if (DownloadContent.IsUgoira) + { + DownloadGif(); + return; + } + + try + { + await using var memory = await PixivIO.Download(DownloadContent.GetDownloadUrl(), new Progress(d => Progress = d), cancellationTokenSource.Token); + if (cancellationTokenSource.IsCancellationRequested) + { + return; + } + await using var fileStream = new FileStream(downloadPath, FileMode.OpenOrCreate, FileAccess.ReadWrite, FileShare.None); + memory.WriteTo(fileStream); + DownloadState.Value = DownloadStateEnum.Finished; + } + catch (OperationCanceledException) + { + if (downloadPath != null && File.Exists(downloadPath)) + { + File.Delete(downloadPath); + } + } + catch (Exception e) + { + if (!retried) + { + Restart(); + retried = true; + } + else + { + HandleError(e, downloadPath); + } + } + } + + private async void DownloadGif() + { + var downloadPath = GetPath(); + try + { + var metadata = await HttpClientFactory.AppApiService().GetUgoiraMetadata(DownloadContent.Id); + var ugoiraUrl = metadata.UgoiraMetadataInfo.ZipUrls.Medium; + ugoiraUrl = !ugoiraUrl.EndsWith("ugoira1920x1080.zip") ? Regex.Replace(ugoiraUrl, "ugoira(\\d+)x(\\d+).zip", "ugoira1920x1080.zip") : ugoiraUrl; + var delay = metadata.UgoiraMetadataInfo.Frames.Select(f => f.Delay / 10).ToArray(); + if (cancellationTokenSource.IsCancellationRequested) + { + return; + } + await using var memory = await PixivIO.Download(ugoiraUrl, new Progress(d => Progress = d), cancellationTokenSource.Token); + await using var gifStream = (MemoryStream) InternalIO.MergeGifStream(InternalIO.ReadGifZipEntries(memory), delay); + if (cancellationTokenSource.IsCancellationRequested) + { + return; + } + await using var fileStream = new FileStream(downloadPath, FileMode.OpenOrCreate, FileAccess.ReadWrite, FileShare.None); + gifStream.WriteTo(fileStream); + DownloadState.Value = DownloadStateEnum.Finished; + } + catch (TaskCanceledException) + { + if (downloadPath != null && File.Exists(downloadPath)) + { + File.Delete(downloadPath); + } + } + catch (Exception e) + { + if (!retried) + { + Restart(); + retried = true; + } + else + { + HandleError(e, downloadPath); + } + } + } + + private void HandleError(Exception e, string path) + { + DownloadState.Value = DownloadStateEnum.Exceptional; + DownloadFailed = true; + ReasonPhase = e.Message; + if (path != null && File.Exists(path)) + { + File.Delete(path); + } + } + } + + [Flags] + public enum DownloadStateEnum + { + Queue, Downloading, Exceptional, Finished, Canceled + } +} \ No newline at end of file diff --git a/Pixeval/Data/ViewModel/I18nOption.cs b/Pixeval/Data/ViewModel/I18nOption.cs new file mode 100644 index 00000000..c00d04da --- /dev/null +++ b/Pixeval/Data/ViewModel/I18nOption.cs @@ -0,0 +1,46 @@ +#region Copyright (C) 2019-2020 Dylech30th. All rights reserved. + +// Pixeval - A Strong, Fast and Flexible Pixiv Client +// Copyright (C) 2019-2020 Dylech30th +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as +// published by the Free Software Foundation, either version 3 of the +// License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see . + +#endregion + +using PropertyChanged; + +namespace Pixeval.Data.ViewModel +{ + [AddINotifyPropertyChangedInterface] + public class I18NOption + { + public static readonly I18NOption UsEnglish = new I18NOption("English(US)", "en-us"); + + public static readonly I18NOption ChineseSimplified = new I18NOption("简体中文(中国)", "zh-cn"); + + public I18NOption(string localizedName, string name) + { + LocalizedName = localizedName; + Name = name; + } + + public I18NOption() + { + } + + public string LocalizedName { get; set; } + + public string Name { get; set; } + } +} \ No newline at end of file diff --git a/Pixeval/Data/ViewModel/Illustration.cs b/Pixeval/Data/ViewModel/Illustration.cs new file mode 100644 index 00000000..3267b1c9 --- /dev/null +++ b/Pixeval/Data/ViewModel/Illustration.cs @@ -0,0 +1,119 @@ +#region Copyright (C) 2019-2020 Dylech30th. All rights reserved. + +// Pixeval - A Strong, Fast and Flexible Pixiv Client +// Copyright (C) 2019-2020 Dylech30th +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as +// published by the Free Software Foundation, either version 3 of the +// License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see . + +#endregion + +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text.RegularExpressions; +using Pixeval.Objects.Primitive; +using PropertyChanged; + +namespace Pixeval.Data.ViewModel +{ + [AddINotifyPropertyChangedInterface] + public class Illustration : ICloneable + { + public string Id { get; set; } + + public bool IsUgoira { get; set; } + + public bool IsR18 => Tags?.Any(x => Regex.IsMatch(x?.Name ?? string.Empty, "[Rr][-]?18[Gg]?") || Regex.IsMatch(x?.TranslatedName ?? string.Empty, "[Rr][-]?18[Gg]?")) ?? false; + + public string Origin { get; set; } + + public string Large { get; set; } + + public string Thumbnail { get; set; } + + public int Bookmark { get; set; } + + public bool IsLiked { get; set; } + + public bool IsManga { get; set; } + + public string Title { get; set; } + + public string UserName { get; set; } + + public string UserId { get; set; } + + public IEnumerable Tags { get; set; } + + public Illustration[] MangaMetadata { get; set; } + + public DateTimeOffset PublishDate { get; set; } + + public int ViewCount { get; set; } + + public string Resolution { get; set; } + + public int Comments { get; set; } + + public bool FromSpotlight { get; set; } + + public string SpotlightTitle { get; set; } + + public object Clone() + { + return MemberwiseClone(); + } + + public string GetDownloadUrl() + { + return Origin.IsNullOrEmpty() ? Large : Origin; + } + } + + public class Tag + { + public string Name { get; set; } + + public string TranslatedName { get; set; } + } + + public class IllustrationPopularityComparator : IComparer + { + public static readonly IllustrationPopularityComparator Instance = new IllustrationPopularityComparator(); + + public int Compare(Illustration x, Illustration y) + { + if (x == null || y == null) + { + return 0; + } + + return x.Bookmark < y.Bookmark ? 1 : x.Bookmark == y.Bookmark ? 0 : -1; + } + } + + public class IllustrationPublishDateComparator : IComparer + { + public static readonly IllustrationPublishDateComparator Instance = new IllustrationPublishDateComparator(); + + public int Compare(Illustration x, Illustration y) + { + if (x == null || y == null) + { + return 0; + } + return x.PublishDate < y.PublishDate ? 1 : x.PublishDate == y.PublishDate ? 0 : -1; + } + } +} \ No newline at end of file diff --git a/Pixeval/Data/ViewModel/RankOptionModel.cs b/Pixeval/Data/ViewModel/RankOptionModel.cs new file mode 100644 index 00000000..282226c9 --- /dev/null +++ b/Pixeval/Data/ViewModel/RankOptionModel.cs @@ -0,0 +1,60 @@ +#region Copyright (C) 2019-2020 Dylech30th. All rights reserved. + +// Pixeval - A Strong, Fast and Flexible Pixiv Client +// Copyright (C) 2019-2020 Dylech30th +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as +// published by the Free Software Foundation, either version 3 of the +// License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see . + +#endregion + +using System; +using System.Linq; +using Pixeval.Core; +using Pixeval.Objects.Generic; +using Pixeval.Objects.I18n; +using Pixeval.Objects.Primitive; +using PropertyChanged; + +namespace Pixeval.Data.ViewModel +{ + [AddINotifyPropertyChangedInterface] + public class RankOptionModel + { + [DoNotNotify] + public static readonly RankOptionModel[] RegularRankOptions = Enum.GetValues(typeof(RankOption)).Cast().Select(rank => new RankOptionModel(rank)).ToArray().Apply(models => models[0].IsSelected = true); + + [DoNotNotify] + public static readonly DateTime MaxRankDateTime = DateTime.Today - TimeSpan.FromDays(2); + + [DoNotNotify] + public static readonly DateTime InvalidRankDateTimeStart = DateTime.Today - TimeSpan.FromDays(1); + + public RankOptionModel(RankOption option) + { + Corresponding = option; + Name = AkaI18N.GetResource(option.GetEnumAttribute().Name); + } + + public string Name { get; set; } + + public bool IsSelected { get; set; } + + public RankOption Corresponding { get; set; } + + public override string ToString() + { + return Name; + } + } +} \ No newline at end of file diff --git a/Pixeval/Data/ViewModel/SearchTagMatchOptionModel.cs b/Pixeval/Data/ViewModel/SearchTagMatchOptionModel.cs new file mode 100644 index 00000000..1b2bf1bf --- /dev/null +++ b/Pixeval/Data/ViewModel/SearchTagMatchOptionModel.cs @@ -0,0 +1,50 @@ +#region Copyright (C) 2019-2020 Dylech30th. All rights reserved. + +// Pixeval - A Strong, Fast and Flexible Pixiv Client +// Copyright (C) 2019-2020 Dylech30th +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as +// published by the Free Software Foundation, either version 3 of the +// License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see . + +#endregion + +using System.Collections.Generic; +using Pixeval.Core; +using Pixeval.Objects.I18n; +using Pixeval.Objects.Primitive; +using PropertyChanged; + +namespace Pixeval.Data.ViewModel +{ + [AddINotifyPropertyChangedInterface] + public class SearchTagMatchOptionModel + { + public static readonly SearchTagMatchOptionModel PartialMatchModel = new SearchTagMatchOptionModel(SearchTagMatchOption.PartialMatchForTags); + + public static readonly SearchTagMatchOptionModel ExactMatchModel = new SearchTagMatchOptionModel(SearchTagMatchOption.ExactMatchForTags); + + public static readonly SearchTagMatchOptionModel TitleAndCaptionModel = new SearchTagMatchOptionModel(SearchTagMatchOption.TitleAndCaption); + + public static readonly IEnumerable AllPossibleMatchOptions = new[] { PartialMatchModel, ExactMatchModel, TitleAndCaptionModel }; + + public SearchTagMatchOptionModel(SearchTagMatchOption corresponding) + { + Description = AkaI18N.GetResource(corresponding.GetEnumAttribute().Name); + Corresponding = corresponding; + } + + public string Description { get; set; } + + public SearchTagMatchOption Corresponding { get; set; } + } +} \ No newline at end of file diff --git a/Pixeval/Data/ViewModel/SpotlightArticle.cs b/Pixeval/Data/ViewModel/SpotlightArticle.cs new file mode 100644 index 00000000..edb16dc2 --- /dev/null +++ b/Pixeval/Data/ViewModel/SpotlightArticle.cs @@ -0,0 +1,72 @@ +#region Copyright (C) 2019-2020 Dylech30th. All rights reserved. + +// Pixeval - A Strong, Fast and Flexible Pixiv Client +// Copyright (C) 2019-2020 Dylech30th +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as +// published by the Free Software Foundation, either version 3 of the +// License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see . + +#endregion + +using System; +using Newtonsoft.Json; +using Pixeval.Core; +using Pixeval.Objects.Generic; +using PropertyChanged; + +namespace Pixeval.Data.ViewModel +{ + [AddINotifyPropertyChangedInterface] + public class SpotlightArticle + { + [JsonProperty("id")] + public long Id { get; set; } + + [JsonProperty("title")] + public string Title { get; set; } + + [JsonProperty("pure_title")] + public string PureTitle { get; set; } + + [JsonProperty("thumbnail")] + public string Thumbnail { get; set; } + + [JsonProperty("article_url")] + public string ArticleUrl { get; set; } + + [JsonProperty("publish_date")] + public DateTimeOffset PublishDate { get; set; } + + [JsonProperty("category")] + public string Category { get; set; } + + [JsonProperty("subcategory_label")] + public string SubcategoryLabel { get; set; } + + public async void Download() + { + var result = await Tasks.Of(await PixivClient.Instance.GetArticleWorks(Id.ToString())).Mapping(async i => + { + var res = await PixivHelper.IllustrationInfo(i); + res.SpotlightTitle = Title; + res.FromSpotlight = true; + return res; + }).Construct().WhenAll(); + + foreach (var illustration in result) + { + DownloadManager.EnqueueDownloadItem(illustration); + } + } + } +} \ No newline at end of file diff --git a/Pixeval/Data/ViewModel/TrendingTag.cs b/Pixeval/Data/ViewModel/TrendingTag.cs new file mode 100644 index 00000000..582da49b --- /dev/null +++ b/Pixeval/Data/ViewModel/TrendingTag.cs @@ -0,0 +1,34 @@ +#region Copyright (C) 2019-2020 Dylech30th. All rights reserved. + +// Pixeval - A Strong, Fast and Flexible Pixiv Client +// Copyright (C) 2019-2020 Dylech30th +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as +// published by the Free Software Foundation, either version 3 of the +// License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see . + +#endregion + +using PropertyChanged; + +namespace Pixeval.Data.ViewModel +{ + [AddINotifyPropertyChangedInterface] + public class TrendingTag + { + public string Tag { get; set; } + + public string TranslatedName { get; set; } + + public string Thumbnail { get; set; } + } +} \ No newline at end of file diff --git a/Pixeval/Data/ViewModel/Trends.cs b/Pixeval/Data/ViewModel/Trends.cs new file mode 100644 index 00000000..f7f8018c --- /dev/null +++ b/Pixeval/Data/ViewModel/Trends.cs @@ -0,0 +1,67 @@ +#region Copyright (C) 2019-2020 Dylech30th. All rights reserved. + +// Pixeval - A Strong, Fast and Flexible Pixiv Client +// Copyright (C) 2019-2020 Dylech30th +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as +// published by the Free Software Foundation, either version 3 of the +// License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see . + +#endregion + +using System; +using PropertyChanged; + +namespace Pixeval.Data.ViewModel +{ + [AddINotifyPropertyChangedInterface] + public class Trends + { + public string PostUserId { get; set; } + + public string PostUserName { get; set; } + + public string TrendObjectId { get; set; } + + public DateTime PostDate { get; set; } + + public TrendType Type { get; set; } + + public string ByName { get; set; } + + public bool IsReferToUser { get; set; } + + public string TrendObjName { get; set; } + + public string TrendObjectThumbnail { get; set; } + + public string PostUserThumbnail { get; set; } + } + + public enum TrendType + { + /// + /// Bookmark + /// + AddBookmark, + + /// + /// New illust + /// + AddIllust, + + /// + /// New follow + /// + AddFavorite + } +} \ No newline at end of file diff --git a/Pixeval/Data/ViewModel/User.cs b/Pixeval/Data/ViewModel/User.cs new file mode 100644 index 00000000..8d520bbc --- /dev/null +++ b/Pixeval/Data/ViewModel/User.cs @@ -0,0 +1,46 @@ +#region Copyright (C) 2019-2020 Dylech30th. All rights reserved. + +// Pixeval - A Strong, Fast and Flexible Pixiv Client +// Copyright (C) 2019-2020 Dylech30th +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as +// published by the Free Software Foundation, either version 3 of the +// License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see . + +#endregion + +using PropertyChanged; + +namespace Pixeval.Data.ViewModel +{ + [AddINotifyPropertyChangedInterface] + public class User + { + public string Name { get; set; } + + public string Id { get; set; } + + public bool IsFollowed { get; set; } + + public string Avatar { get; set; } + + public string Introduction { get; set; } + + public string Background { get; set; } + + public int Follows { get; set; } + + public bool IsPremium { get; set; } + + public string[] Thumbnails { get; set; } = new string[3]; + } +} \ No newline at end of file diff --git a/Pixeval/Data/Web/Delegation/DnsResolvedHttpClientHandler.cs b/Pixeval/Data/Web/Delegation/DnsResolvedHttpClientHandler.cs new file mode 100644 index 00000000..bfee0f93 --- /dev/null +++ b/Pixeval/Data/Web/Delegation/DnsResolvedHttpClientHandler.cs @@ -0,0 +1,95 @@ +#region Copyright (C) 2019-2020 Dylech30th. All rights reserved. + +// Pixeval - A Strong, Fast and Flexible Pixiv Client +// Copyright (C) 2019-2020 Dylech30th +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as +// published by the Free Software Foundation, either version 3 of the +// License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see . + +#endregion + +using System; +using System.Net; +using System.Net.Http; +using System.Net.Http.Headers; +using System.Threading; +using System.Threading.Tasks; +using Pixeval.Persisting; + +namespace Pixeval.Data.Web.Delegation +{ + public abstract class DnsResolvedHttpClientHandler : HttpClientHandler + { + private readonly bool directConnect; + private readonly IHttpRequestHandler requestHandler; + + static DnsResolvedHttpClientHandler() + { + System.AppContext.SetSwitch("System.Net.Http.UseSocketsHttpHandler", false); + } + + protected DnsResolvedHttpClientHandler(IHttpRequestHandler requestHandler = null, bool directConnect = true) + { + this.requestHandler = requestHandler; + this.directConnect = directConnect; + ServerCertificateCustomValidationCallback = DangerousAcceptAnyServerCertificateValidator; + } + + protected abstract DnsResolver DnsResolver { get; set; } + + protected override async Task SendAsync(HttpRequestMessage request, CancellationToken cancellationToken) + { + requestHandler?.Handle(request); + + if (directConnect) + { + var host = request.RequestUri.DnsSafeHost; + + var isSslSession = request.RequestUri.ToString().StartsWith("https://"); + + request.RequestUri = new Uri($"{(isSslSession ? "https://" : "http://")}{DnsResolver.Lookup()[0]}{request.RequestUri.PathAndQuery}"); + request.Headers.Host = host; + } + + HttpResponseMessage result; + try + { + result = await base.SendAsync(request, cancellationToken); + } + catch (HttpRequestException e) + { + if (e.InnerException != null && e.InnerException.Message.ToLower().Contains("winhttp")) + { + return new HttpResponseMessage(HttpStatusCode.OK); + } + throw; + } + + if (result.StatusCode == HttpStatusCode.BadRequest && (await result.Content.ReadAsStringAsync()).Contains("OAuth")) + { + using var semaphore = new SemaphoreSlim(1); + await semaphore.WaitAsync(cancellationToken); + await Authentication.AppApiAuthenticate(Session.Current.Account, Session.Current.Password); + var token = request.Headers.Authorization; + if (token != null) + { + request.Headers.Authorization = new AuthenticationHeaderValue(token.Scheme, Session.Current.AccessToken); + } + + return await base.SendAsync(request, cancellationToken); + } + + return result; + } + } +} \ No newline at end of file diff --git a/Pixeval/Data/Web/Delegation/DnsResolver.cs b/Pixeval/Data/Web/Delegation/DnsResolver.cs new file mode 100644 index 00000000..68ada4e6 --- /dev/null +++ b/Pixeval/Data/Web/Delegation/DnsResolver.cs @@ -0,0 +1,138 @@ +#region Copyright (C) 2019-2020 Dylech30th. All rights reserved. + +// Pixeval - A Strong, Fast and Flexible Pixiv Client +// Copyright (C) 2019-2020 Dylech30th +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as +// published by the Free Software Foundation, either version 3 of the +// License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see . + +#endregion + +using System; +using System.Collections.Generic; +using System.Collections.Immutable; +using System.Net; +using System.Net.Http; +using System.Security.Authentication; +using System.Threading; +using System.Threading.Tasks; +using Pixeval.Data.Web.Protocol; +using Pixeval.Data.Web.Request; +using Pixeval.Data.Web.Response; +using Refit; + +namespace Pixeval.Data.Web.Delegation +{ + public abstract class DnsResolver + { + public static readonly ThreadLocal>> DnsCache = new ThreadLocal>>(() => new Dictionary>()); + + protected async Task GetDnsJson(string hostname) + { + return await RestService.For(new HttpClient(new HttpClientHandler { SslProtocols = SslProtocols.Tls12 | SslProtocols.Tls11 | SslProtocols.Tls }) { BaseAddress = new Uri(ProtocolBase.DnsServer), Timeout = TimeSpan.FromSeconds(5) }).ResolveDns(new DnsResolveRequest + { + Ct = "application/dns-json", + Cd = "false", + Do = "false", + Name = hostname, + Type = "A" + }); + } + + /*public async Task> Lookup(string hostname) + { + const string OAuthUrl = "oauth.secure.pixiv.net"; + if (hostname == OAuthUrl) + { + CacheDns(hostname, UseDefaultDns()); + return DnsCache.Value[hostname]; + } + + if (DnsCache.Value.ContainsKey(hostname)) return DnsCache.Value[hostname].ToImmutableList(); + + var ipList = new HashSet(new IpAddressEqualityComparer()); + if (_dnsQueryFailed) + { + CacheDns(hostname, UseDefaultDns()); + return DnsCache.Value[hostname]; + } + + DnsResolveResponse response; + try + { + response = await GetDnsJson(hostname); + } + catch (Exception) + { + _dnsQueryFailed = true; + CacheDns(hostname, UseDefaultDns()); + return DnsCache.Value[hostname]; + } + + if (response != null) + { + var answer = response.Answers; + if (!answer.IsNullOrEmpty()) + { + foreach (var queriedIp in answer) + if (IPAddress.TryParse(queriedIp.Data, out var address)) + ipList.Add(address); + } + else + { + ipList.AddRange(await Dns.GetHostAddressesAsync(hostname)); + if (ipList.IsNullOrEmpty()) ipList.AddRange(UseDefaultDns()); + } + + CacheDns(hostname, ipList); + return ipList.ToImmutableList(); + } + + ipList.AddRange(UseDefaultDns()); + CacheDns(hostname, ipList); + return ipList.ToImmutableList(); + }*/ + + public IReadOnlyList Lookup() + { + return UseDefaultDns().ToImmutableList(); + } + + // private static void CacheDns(string hostname, IEnumerable ipList) + // { + // if (DnsCache.Value.ContainsKey(hostname)) + // DnsCache.Value[hostname].AddRange(ipList); + // else + // DnsCache.Value[hostname] = new List(ipList); + // } + + protected abstract IEnumerable UseDefaultDns(); + + private class IpAddressEqualityComparer : IEqualityComparer + { + public bool Equals(IPAddress x, IPAddress y) + { + if (x == null || y == null) + { + return false; + } + return x.ToString() == y.ToString(); + } + + public int GetHashCode(IPAddress obj) + { + return obj.GetHashCode(); + } + } + } +} \ No newline at end of file diff --git a/Pixeval/Data/Web/Delegation/HttpClientFactory.cs b/Pixeval/Data/Web/Delegation/HttpClientFactory.cs new file mode 100644 index 00000000..acdf9135 --- /dev/null +++ b/Pixeval/Data/Web/Delegation/HttpClientFactory.cs @@ -0,0 +1,72 @@ +#region Copyright (C) 2019-2020 Dylech30th. All rights reserved. + +// Pixeval - A Strong, Fast and Flexible Pixiv Client +// Copyright (C) 2019-2020 Dylech30th +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as +// published by the Free Software Foundation, either version 3 of the +// License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see . + +#endregion + +using System; +using System.Net.Http; +using System.Threading.Tasks; +using Pixeval.Data.Web.Protocol; +using Pixeval.Objects.Generic; +using Pixeval.Persisting; +using Refit; + +namespace Pixeval.Data.Web.Delegation +{ + public class HttpClientFactory + { + public static HttpClient AppApiHttpClient() + { + return PixivApi(ProtocolBase.AppApiBaseUrl, Settings.Global.DirectConnect).Apply(h => h.DefaultRequestHeaders.Add("Authorization", "Bearer")); + } + + public static HttpClient WebApiHttpClient() + { + return PixivApi(ProtocolBase.WebApiBaseUrl, Settings.Global.DirectConnect); + } + + public static IAppApiProtocol AppApiService() + { + return RestService.For(PixivApi(ProtocolBase.AppApiBaseUrl, Settings.Global.DirectConnect)); + } + + public static IWebApiProtocol WebApiService() + { + return RestService.For(PixivApi(ProtocolBase.WebApiBaseUrl, Settings.Global.DirectConnect)); + } + + public static HttpClient PixivApi(string baseAddress, bool directConnect) + { + return new HttpClient(PixivApiHttpClientHandler.Instance(directConnect)) { BaseAddress = new Uri(baseAddress) }; + } + + public static HttpClient PixivImage() + { + return new HttpClient(PixivImageHttpClientHandler.Instance).Apply(client => + { + client.DefaultRequestHeaders.TryAddWithoutValidation("Referer", "http://www.pixiv.net"); + client.DefaultRequestHeaders.TryAddWithoutValidation("User-Agent", "PixivIOSApp/5.8.7"); + }); + } + + public static Task GetResponseHeader(HttpClient client, string uri) + { + return client.GetAsync(uri, HttpCompletionOption.ResponseHeadersRead); + } + } +} \ No newline at end of file diff --git a/Pixeval/Data/Web/Delegation/IHttpRequestHandler.cs b/Pixeval/Data/Web/Delegation/IHttpRequestHandler.cs new file mode 100644 index 00000000..8ff85ae1 --- /dev/null +++ b/Pixeval/Data/Web/Delegation/IHttpRequestHandler.cs @@ -0,0 +1,29 @@ +#region Copyright (C) 2019-2020 Dylech30th. All rights reserved. + +// Pixeval - A Strong, Fast and Flexible Pixiv Client +// Copyright (C) 2019-2020 Dylech30th +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as +// published by the Free Software Foundation, either version 3 of the +// License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see . + +#endregion + +using System.Net.Http; + +namespace Pixeval.Data.Web.Delegation +{ + public interface IHttpRequestHandler + { + void Handle(HttpRequestMessage httpRequestMessage); + } +} \ No newline at end of file diff --git a/Pixeval/Data/Web/Delegation/PixivApiDnsResolver.cs b/Pixeval/Data/Web/Delegation/PixivApiDnsResolver.cs new file mode 100644 index 00000000..40a72f6b --- /dev/null +++ b/Pixeval/Data/Web/Delegation/PixivApiDnsResolver.cs @@ -0,0 +1,37 @@ +#region Copyright (C) 2019-2020 Dylech30th. All rights reserved. + +// Pixeval - A Strong, Fast and Flexible Pixiv Client +// Copyright (C) 2019-2020 Dylech30th +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as +// published by the Free Software Foundation, either version 3 of the +// License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see . + +#endregion + +using System.Collections.Generic; +using System.Net; + +namespace Pixeval.Data.Web.Delegation +{ + public class PixivApiDnsResolver : DnsResolver + { + public static readonly DnsResolver Instance = new PixivApiDnsResolver(); + + protected override IEnumerable UseDefaultDns() + { + yield return IPAddress.Parse("210.140.131.219"); + yield return IPAddress.Parse("210.140.131.223"); + yield return IPAddress.Parse("210.140.131.226"); + } + } +} \ No newline at end of file diff --git a/Pixeval/Data/Web/Delegation/PixivApiHttpClientHandler.cs b/Pixeval/Data/Web/Delegation/PixivApiHttpClientHandler.cs new file mode 100644 index 00000000..89b3b11e --- /dev/null +++ b/Pixeval/Data/Web/Delegation/PixivApiHttpClientHandler.cs @@ -0,0 +1,38 @@ +#region Copyright (C) 2019-2020 Dylech30th. All rights reserved. + +// Pixeval - A Strong, Fast and Flexible Pixiv Client +// Copyright (C) 2019-2020 Dylech30th +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as +// published by the Free Software Foundation, either version 3 of the +// License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see . + +#endregion + +using System.Net.Http; + +namespace Pixeval.Data.Web.Delegation +{ + public class PixivApiHttpClientHandler : DnsResolvedHttpClientHandler + { + private PixivApiHttpClientHandler(bool directConnect) : base(PixivHttpRequestHandler.Instance, directConnect) + { + } + + protected override DnsResolver DnsResolver { get; set; } = PixivApiDnsResolver.Instance; + + public static HttpMessageHandler Instance(bool directConnect) + { + return new PixivApiHttpClientHandler(directConnect); + } + } +} \ No newline at end of file diff --git a/Pixeval/Data/Web/Delegation/PixivHttpRequestHandler.cs b/Pixeval/Data/Web/Delegation/PixivHttpRequestHandler.cs new file mode 100644 index 00000000..d832e99c --- /dev/null +++ b/Pixeval/Data/Web/Delegation/PixivHttpRequestHandler.cs @@ -0,0 +1,72 @@ +#region Copyright (C) 2019-2020 Dylech30th. All rights reserved. + +// Pixeval - A Strong, Fast and Flexible Pixiv Client +// Copyright (C) 2019-2020 Dylech30th +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as +// published by the Free Software Foundation, either version 3 of the +// License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see . + +#endregion + +using System; +using System.Net.Http; +using System.Net.Http.Headers; +using Pixeval.Objects.Exceptions; +using Pixeval.Objects.I18n; +using Pixeval.Objects.Primitive; +using Pixeval.Persisting; + +namespace Pixeval.Data.Web.Delegation +{ + public class PixivHttpRequestHandler : IHttpRequestHandler + { + public static readonly PixivHttpRequestHandler Instance = new PixivHttpRequestHandler(); + + protected PixivHttpRequestHandler() + { + } + + public virtual void Handle(HttpRequestMessage httpRequestMessage) + { + switch (httpRequestMessage.RequestUri.DnsSafeHost) + { + case "app-api.pixiv.net": + var token = httpRequestMessage.Headers.Authorization; + if (token != null) + { + if (Session.Current.AccessToken.IsNullOrEmpty()) + { + throw new TokenNotFoundException($"{nameof(Session.Current.AccessToken)} is empty, this exception should never be thrown, if you see this message, please send issue on github or contact me (decem0730@gmail.com)"); + } + + httpRequestMessage.Headers.Authorization = new AuthenticationHeaderValue(token.Scheme, Session.Current.AccessToken); + } + + break; + case var x when x == "pixiv.net" || x == "www.pixiv.net": + httpRequestMessage.Headers.TryAddWithoutValidation("Cookie", Settings.Global.Cookie); + break; + } + + if (httpRequestMessage.RequestUri.DnsSafeHost == "i.pximg.net" && !Settings.Global.MirrorServer.IsNullOrEmpty()) + { + httpRequestMessage.RequestUri = new Uri(httpRequestMessage.RequestUri.ToString().Replace("i.pximg.net", Settings.Global.MirrorServer)); + } + + if (!httpRequestMessage.Headers.Contains("Accept-Language")) + { + httpRequestMessage.Headers.TryAddWithoutValidation("Accept-Language", AkaI18N.GetCultureAcceptLanguage()); + } + } + } +} \ No newline at end of file diff --git a/Pixeval/Data/Web/Delegation/PixivImageHttpClientHandler.cs b/Pixeval/Data/Web/Delegation/PixivImageHttpClientHandler.cs new file mode 100644 index 00000000..c3701a6e --- /dev/null +++ b/Pixeval/Data/Web/Delegation/PixivImageHttpClientHandler.cs @@ -0,0 +1,35 @@ +#region Copyright (C) 2019-2020 Dylech30th. All rights reserved. + +// Pixeval - A Strong, Fast and Flexible Pixiv Client +// Copyright (C) 2019-2020 Dylech30th +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as +// published by the Free Software Foundation, either version 3 of the +// License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see . + +#endregion + +using System.Net.Http; + +namespace Pixeval.Data.Web.Delegation +{ + public class PixivImageHttpClientHandler : DnsResolvedHttpClientHandler + { + public static readonly HttpMessageHandler Instance = new PixivImageHttpClientHandler(); + + private PixivImageHttpClientHandler() : base(PixivHttpRequestHandler.Instance, false) + { + } + + protected override DnsResolver DnsResolver { get; set; } = null; + } +} \ No newline at end of file diff --git a/Pixeval/Data/Web/HttpResponse.cs b/Pixeval/Data/Web/HttpResponse.cs new file mode 100644 index 00000000..b3ea1460 --- /dev/null +++ b/Pixeval/Data/Web/HttpResponse.cs @@ -0,0 +1,47 @@ +#region Copyright (C) 2019-2020 Dylech30th. All rights reserved. + +// Pixeval - A Strong, Fast and Flexible Pixiv Client +// Copyright (C) 2019-2020 Dylech30th +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as +// published by the Free Software Foundation, either version 3 of the +// License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see . + +#endregion + +using System; + +namespace Pixeval.Data.Web +{ + public class HttpResponse : Tuple + { + private HttpResponse(bool status, T response) : base(status, response) + { + } + + public static HttpResponse Wrap(bool status) + { + return new HttpResponse(status, default); + } + + public static HttpResponse Wrap(bool status, T response) + { + return new HttpResponse(status, response); + } + + public void Deconstruct(out bool status, out T response) + { + status = Item1; + response = Item2; + } + } +} \ No newline at end of file diff --git a/Pixeval/Data/Web/Protocol/IAppApiProtocol.cs b/Pixeval/Data/Web/Protocol/IAppApiProtocol.cs new file mode 100644 index 00000000..5a5cbc0f --- /dev/null +++ b/Pixeval/Data/Web/Protocol/IAppApiProtocol.cs @@ -0,0 +1,64 @@ +#region Copyright (C) 2019-2020 Dylech30th. All rights reserved. + +// Pixeval - A Strong, Fast and Flexible Pixiv Client +// Copyright (C) 2019-2020 Dylech30th +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as +// published by the Free Software Foundation, either version 3 of the +// License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see . + +#endregion + +using System.Threading.Tasks; +using Pixeval.Data.Web.Request; +using Pixeval.Data.Web.Response; +using Refit; + +namespace Pixeval.Data.Web.Protocol +{ + [Headers("Authorization: Bearer")] + public interface IAppApiProtocol + { + [Post("/v1/illust/bookmark/delete")] + Task DeleteBookmark([Body(BodySerializationMethod.UrlEncoded)] DeleteBookmarkRequest deleteBookmarkRequest); + + [Get("/v1/user/detail")] + Task GetUserInformation(UserInformationRequest userInformationRequest); + + [Get("/v1/spotlight/articles?category=all")] + Task GetSpotlights(int offset); + + [Post("/v1/user/follow/add")] + Task FollowArtist([Body(BodySerializationMethod.UrlEncoded)] FollowArtistRequest followArtistRequest); + + [Post("/v1/user/follow/delete")] + Task UnFollowArtist([Body(BodySerializationMethod.UrlEncoded)] UnFollowArtistRequest unFollowArtistRequest); + + [Get("/v1/ugoira/metadata")] + Task GetUgoiraMetadata([AliasAs("illust_id")] string id); + + [Post("/v2/illust/bookmark/add")] + Task AddBookmark([Body(BodySerializationMethod.UrlEncoded)] AddBookmarkRequest addBookmarkRequest); + + [Get("/v2/search/autocomplete")] + Task GetAutoCompletion(AutoCompletionRequest autoCompletionRequest); + + [Get("/v1/illust/detail")] + Task GetSingle([AliasAs("illust_id")] string id); + + [Get("/v1/user/recommended?filter=for_android")] + Task GetRecommendIllustrators(RecommendIllustratorRequest recommendIllustratorRequest); + + [Get("/v1/trending-tags/illust?filter=for_android")] + Task GetTrendingTags(); + } +} \ No newline at end of file diff --git a/Pixeval/Data/Web/Protocol/IResolveDnsProtocol.cs b/Pixeval/Data/Web/Protocol/IResolveDnsProtocol.cs new file mode 100644 index 00000000..90d3a2cb --- /dev/null +++ b/Pixeval/Data/Web/Protocol/IResolveDnsProtocol.cs @@ -0,0 +1,33 @@ +#region Copyright (C) 2019-2020 Dylech30th. All rights reserved. + +// Pixeval - A Strong, Fast and Flexible Pixiv Client +// Copyright (C) 2019-2020 Dylech30th +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as +// published by the Free Software Foundation, either version 3 of the +// License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see . + +#endregion + +using System.Threading.Tasks; +using Pixeval.Data.Web.Request; +using Pixeval.Data.Web.Response; +using Refit; + +namespace Pixeval.Data.Web.Protocol +{ + public interface IResolveDnsProtocol + { + [Get("/dns-query")] + Task ResolveDns(DnsResolveRequest dnsResolverRequest); + } +} \ No newline at end of file diff --git a/Pixeval/Data/Web/Protocol/ISauceNAOProtocol.cs b/Pixeval/Data/Web/Protocol/ISauceNAOProtocol.cs new file mode 100644 index 00000000..d7a48dde --- /dev/null +++ b/Pixeval/Data/Web/Protocol/ISauceNAOProtocol.cs @@ -0,0 +1,34 @@ +#region Copyright (C) 2019-2020 Dylech30th. All rights reserved. + +// Pixeval - A Strong, Fast and Flexible Pixiv Client +// Copyright (C) 2019-2020 Dylech30th +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as +// published by the Free Software Foundation, either version 3 of the +// License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see . + +#endregion + +using System.Net.Http; +using System.Threading.Tasks; +using Refit; + +namespace Pixeval.Data.Web.Protocol +{ + // ReSharper disable once InconsistentNaming + public interface ISauceNAOProtocol + { + [Multipart] + [Post("/search.php")] + Task GetSauce([AliasAs("file")] StreamPart stream); + } +} \ No newline at end of file diff --git a/Pixeval/Data/Web/Protocol/ITokenProtocol.cs b/Pixeval/Data/Web/Protocol/ITokenProtocol.cs new file mode 100644 index 00000000..117a8eda --- /dev/null +++ b/Pixeval/Data/Web/Protocol/ITokenProtocol.cs @@ -0,0 +1,37 @@ +#region Copyright (C) 2019-2020 Dylech30th. All rights reserved. + +// Pixeval - A Strong, Fast and Flexible Pixiv Client +// Copyright (C) 2019-2020 Dylech30th +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as +// published by the Free Software Foundation, either version 3 of the +// License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see . + +#endregion + +using System.Threading.Tasks; +using Pixeval.Data.Web.Request; +using Pixeval.Data.Web.Response; +using Refit; + +namespace Pixeval.Data.Web.Protocol +{ + [Headers("User-Agent: PixivAndroidApp/5.0.64 (Android 6.0)", "Content-Type: application/x-www-form-urlencoded")] + public interface ITokenProtocol + { + [Post("/auth/token")] + Task GetTokenByPassword([Body(BodySerializationMethod.UrlEncoded)] PasswordTokenRequest body, [Header("X-Client-Time")] string clientTime, [Header("X-Client-Hash")] string clientHash); + + [Post("/auth/token")] + Task RefreshToken([Body(BodySerializationMethod.UrlEncoded)] RefreshTokenRequest body); + } +} \ No newline at end of file diff --git a/Pixeval/Data/Web/Protocol/IWebApiProtocol.cs b/Pixeval/Data/Web/Protocol/IWebApiProtocol.cs new file mode 100644 index 00000000..0f0af6a4 --- /dev/null +++ b/Pixeval/Data/Web/Protocol/IWebApiProtocol.cs @@ -0,0 +1,41 @@ +#region Copyright (C) 2019-2020 Dylech30th. All rights reserved. + +// Pixeval - A Strong, Fast and Flexible Pixiv Client +// Copyright (C) 2019-2020 Dylech30th +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as +// published by the Free Software Foundation, either version 3 of the +// License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see . + +#endregion + +using System.Net.Http; +using System.Threading.Tasks; +using Pixeval.Data.Web.Request; +using Pixeval.Data.Web.Response; +using Refit; + +namespace Pixeval.Data.Web.Protocol +{ + [Headers("User-Agent: PixivAndroidApp/5.0.64 (Android 6.0)", "Content-Type: application/x-www-form-urlencoded")] + public interface IWebApiProtocol + { + [Post("/setting_user.php")] + Task ToggleR18State([Body(BodySerializationMethod.UrlEncoded)] ToggleR18StateRequest toggleR18StateRequest); + + [Get("/ajax/showcase/article")] + Task GetSpotlightArticles([AliasAs("article_id")] string articleId); + + [Get("/touch/ajax/user/details")] + Task GetWebApiUserDetail(string id); + } +} \ No newline at end of file diff --git a/Pixeval/Data/Web/ProtocolBase.cs b/Pixeval/Data/Web/ProtocolBase.cs new file mode 100644 index 00000000..03b8ac2b --- /dev/null +++ b/Pixeval/Data/Web/ProtocolBase.cs @@ -0,0 +1,37 @@ +#region Copyright (C) 2019-2020 Dylech30th. All rights reserved. + +// Pixeval - A Strong, Fast and Flexible Pixiv Client +// Copyright (C) 2019-2020 Dylech30th +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as +// published by the Free Software Foundation, either version 3 of the +// License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see . + +#endregion + +namespace Pixeval.Data.Web +{ + public class ProtocolBase + { + public const string PublicApiBaseUrl = "https://public-api.secure.pixiv.net/v1"; + + public const string AppApiBaseUrl = "https://app-api.pixiv.net"; + + public const string DnsServer = "https://1.0.0.1"; + + public const string SauceNaoUrl = "https://saucenao.com/"; + + public const string OAuthBaseUrl = "https://oauth.secure.pixiv.net"; + + public const string WebApiBaseUrl = "https://www.pixiv.net"; + } +} \ No newline at end of file diff --git a/Pixeval/Data/Web/Request/AddBookmarkRequest.cs b/Pixeval/Data/Web/Request/AddBookmarkRequest.cs new file mode 100644 index 00000000..fe7d3e85 --- /dev/null +++ b/Pixeval/Data/Web/Request/AddBookmarkRequest.cs @@ -0,0 +1,33 @@ +#region Copyright (C) 2019-2020 Dylech30th. All rights reserved. + +// Pixeval - A Strong, Fast and Flexible Pixiv Client +// Copyright (C) 2019-2020 Dylech30th +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as +// published by the Free Software Foundation, either version 3 of the +// License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see . + +#endregion + +using Refit; + +namespace Pixeval.Data.Web.Request +{ + public class AddBookmarkRequest + { + [AliasAs("restrict")] + public string Restrict { get; set; } = "public"; + + [AliasAs("illust_id")] + public string Id { get; set; } + } +} \ No newline at end of file diff --git a/Pixeval/Data/Web/Request/AutoCompletionRequest.cs b/Pixeval/Data/Web/Request/AutoCompletionRequest.cs new file mode 100644 index 00000000..f9216668 --- /dev/null +++ b/Pixeval/Data/Web/Request/AutoCompletionRequest.cs @@ -0,0 +1,33 @@ +#region Copyright (C) 2019-2020 Dylech30th. All rights reserved. + +// Pixeval - A Strong, Fast and Flexible Pixiv Client +// Copyright (C) 2019-2020 Dylech30th +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as +// published by the Free Software Foundation, either version 3 of the +// License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see . + +#endregion + +using Refit; + +namespace Pixeval.Data.Web.Request +{ + public class AutoCompletionRequest + { + [AliasAs("merge_plain_keyword_results=true")] + public bool MergePlainKeywordResult { get; set; } = true; + + [AliasAs("word")] + public string Word { get; set; } + } +} \ No newline at end of file diff --git a/Pixeval/Data/Web/Request/DeleteBookmarkRequest.cs b/Pixeval/Data/Web/Request/DeleteBookmarkRequest.cs new file mode 100644 index 00000000..4f94ac2b --- /dev/null +++ b/Pixeval/Data/Web/Request/DeleteBookmarkRequest.cs @@ -0,0 +1,30 @@ +#region Copyright (C) 2019-2020 Dylech30th. All rights reserved. + +// Pixeval - A Strong, Fast and Flexible Pixiv Client +// Copyright (C) 2019-2020 Dylech30th +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as +// published by the Free Software Foundation, either version 3 of the +// License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see . + +#endregion + +using Refit; + +namespace Pixeval.Data.Web.Request +{ + public class DeleteBookmarkRequest + { + [AliasAs("illust_id")] + public string IllustId { get; set; } + } +} \ No newline at end of file diff --git a/Pixeval/Data/Web/Request/DnsResolveRequest.cs b/Pixeval/Data/Web/Request/DnsResolveRequest.cs new file mode 100644 index 00000000..b560cc95 --- /dev/null +++ b/Pixeval/Data/Web/Request/DnsResolveRequest.cs @@ -0,0 +1,42 @@ +#region Copyright (C) 2019-2020 Dylech30th. All rights reserved. + +// Pixeval - A Strong, Fast and Flexible Pixiv Client +// Copyright (C) 2019-2020 Dylech30th +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as +// published by the Free Software Foundation, either version 3 of the +// License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see . + +#endregion + +using Refit; + +namespace Pixeval.Data.Web.Request +{ + public class DnsResolveRequest + { + [AliasAs("ct")] + public string Ct { get; set; } + + [AliasAs("name")] + public string Name { get; set; } + + [AliasAs("type")] + public string Type { get; set; } + + [AliasAs("do")] + public string Do { get; set; } + + [AliasAs("cd")] + public string Cd { get; set; } + } +} \ No newline at end of file diff --git a/Pixeval/Data/Web/Request/FollowArtistRequest.cs b/Pixeval/Data/Web/Request/FollowArtistRequest.cs new file mode 100644 index 00000000..ed93b4d2 --- /dev/null +++ b/Pixeval/Data/Web/Request/FollowArtistRequest.cs @@ -0,0 +1,33 @@ +#region Copyright (C) 2019-2020 Dylech30th. All rights reserved. + +// Pixeval - A Strong, Fast and Flexible Pixiv Client +// Copyright (C) 2019-2020 Dylech30th +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as +// published by the Free Software Foundation, either version 3 of the +// License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see . + +#endregion + +using Refit; + +namespace Pixeval.Data.Web.Request +{ + public class FollowArtistRequest + { + [AliasAs("user_id")] + public string Id { get; set; } + + [AliasAs("restrict")] + public string Restrict { get; set; } = "public"; + } +} \ No newline at end of file diff --git a/Pixeval/Data/Web/Request/PasswordTokenRequest.cs b/Pixeval/Data/Web/Request/PasswordTokenRequest.cs new file mode 100644 index 00000000..432f1e59 --- /dev/null +++ b/Pixeval/Data/Web/Request/PasswordTokenRequest.cs @@ -0,0 +1,45 @@ +#region Copyright (C) 2019-2020 Dylech30th. All rights reserved. + +// Pixeval - A Strong, Fast and Flexible Pixiv Client +// Copyright (C) 2019-2020 Dylech30th +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as +// published by the Free Software Foundation, either version 3 of the +// License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see . + +#endregion + +using Refit; + +namespace Pixeval.Data.Web.Request +{ + public class PasswordTokenRequest + { + [AliasAs("username")] + public string Name { get; set; } + + [AliasAs("password")] + public string Password { get; set; } + + [AliasAs("grant_type")] + public string GrantType => "password"; + + [AliasAs("client_id")] + public string ClientId => "MOBrBDS8blbauoSck0ZfDbtuzpyT"; + + [AliasAs("client_secret")] + public string ClientSecret => "lsACyCD94FhDUtGTXi3QzcFE2uU1hqtDaKeqrdwj"; + + [AliasAs("get_secure_url")] + public string GetSecureUrl => "1"; + } +} \ No newline at end of file diff --git a/Pixeval/Data/Web/Request/RecommendIllustratorRequest.cs b/Pixeval/Data/Web/Request/RecommendIllustratorRequest.cs new file mode 100644 index 00000000..8e3dfb1c --- /dev/null +++ b/Pixeval/Data/Web/Request/RecommendIllustratorRequest.cs @@ -0,0 +1,33 @@ +#region Copyright (C) 2019-2020 Dylech30th. All rights reserved. + +// Pixeval - A Strong, Fast and Flexible Pixiv Client +// Copyright (C) 2019-2020 Dylech30th +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as +// published by the Free Software Foundation, either version 3 of the +// License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see . + +#endregion + +using Refit; + +namespace Pixeval.Data.Web.Request +{ + public class RecommendIllustratorRequest + { + [AliasAs("filter")] + public string Filter { get; } = "for_android"; + + [AliasAs("offset")] + public int Offset { get; set; } = 0; + } +} \ No newline at end of file diff --git a/Pixeval/Data/Web/Request/RefreshTokenRequest.cs b/Pixeval/Data/Web/Request/RefreshTokenRequest.cs new file mode 100644 index 00000000..8a2b3d28 --- /dev/null +++ b/Pixeval/Data/Web/Request/RefreshTokenRequest.cs @@ -0,0 +1,42 @@ +#region Copyright (C) 2019-2020 Dylech30th. All rights reserved. + +// Pixeval - A Strong, Fast and Flexible Pixiv Client +// Copyright (C) 2019-2020 Dylech30th +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as +// published by the Free Software Foundation, either version 3 of the +// License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see . + +#endregion + +using Refit; + +namespace Pixeval.Data.Web.Request +{ + public class RefreshTokenRequest + { + [AliasAs("refresh_token")] + public string RefreshToken { get; set; } + + [AliasAs("grant_type")] + public string GrantType => "refresh_token"; + + [AliasAs("client_id")] + public string ClientId => "MOBrBDS8blbauoSck0ZfDbtuzpyT"; + + [AliasAs("client_secret")] + public string ClientSecret => "lsACyCD94FhDUtGTXi3QzcFE2uU1hqtDaKeqrdwj"; + + [AliasAs("get_secure_url")] + public string GetSecureUrl => "1"; + } +} \ No newline at end of file diff --git a/Pixeval/Data/Web/Request/ToggleR18StateRequest.cs b/Pixeval/Data/Web/Request/ToggleR18StateRequest.cs new file mode 100644 index 00000000..1e2b7f35 --- /dev/null +++ b/Pixeval/Data/Web/Request/ToggleR18StateRequest.cs @@ -0,0 +1,45 @@ +#region Copyright (C) 2019-2020 Dylech30th. All rights reserved. + +// Pixeval - A Strong, Fast and Flexible Pixiv Client +// Copyright (C) 2019-2020 Dylech30th +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as +// published by the Free Software Foundation, either version 3 of the +// License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see . + +#endregion + +using Refit; + +namespace Pixeval.Data.Web.Request +{ + public class ToggleR18StateRequest + { + [AliasAs("mode")] + public string Mode { get; } = "mod"; + + [AliasAs("user_language")] + public string UserLang { get; } = "zh"; + + [AliasAs("r18")] + public string R18 { get; set; } + + [AliasAs("r18g")] + public string R18G { get; set; } + + [AliasAs("submit")] + public string Submit { get; } = "保存"; + + [AliasAs("tt")] + public string Tt { get; set; } + } +} \ No newline at end of file diff --git a/Pixeval/Data/Web/Request/UnFollowArtistRequest.cs b/Pixeval/Data/Web/Request/UnFollowArtistRequest.cs new file mode 100644 index 00000000..e74f6223 --- /dev/null +++ b/Pixeval/Data/Web/Request/UnFollowArtistRequest.cs @@ -0,0 +1,30 @@ +#region Copyright (C) 2019-2020 Dylech30th. All rights reserved. + +// Pixeval - A Strong, Fast and Flexible Pixiv Client +// Copyright (C) 2019-2020 Dylech30th +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as +// published by the Free Software Foundation, either version 3 of the +// License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see . + +#endregion + +using Refit; + +namespace Pixeval.Data.Web.Request +{ + public class UnFollowArtistRequest + { + [AliasAs("user_id")] + public string UserId { get; set; } + } +} \ No newline at end of file diff --git a/Pixeval/Data/Web/Request/UserInformationRequest.cs b/Pixeval/Data/Web/Request/UserInformationRequest.cs new file mode 100644 index 00000000..18576702 --- /dev/null +++ b/Pixeval/Data/Web/Request/UserInformationRequest.cs @@ -0,0 +1,33 @@ +#region Copyright (C) 2019-2020 Dylech30th. All rights reserved. + +// Pixeval - A Strong, Fast and Flexible Pixiv Client +// Copyright (C) 2019-2020 Dylech30th +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as +// published by the Free Software Foundation, either version 3 of the +// License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see . + +#endregion + +using Refit; + +namespace Pixeval.Data.Web.Request +{ + public class UserInformationRequest + { + [AliasAs("user_id")] + public string Id { get; set; } + + [AliasAs("filter")] + public string Filter { get; set; } = "for_android"; + } +} \ No newline at end of file diff --git a/Pixeval/Data/Web/Response/AutoCompletionResponse.cs b/Pixeval/Data/Web/Response/AutoCompletionResponse.cs new file mode 100644 index 00000000..ff04ceb6 --- /dev/null +++ b/Pixeval/Data/Web/Response/AutoCompletionResponse.cs @@ -0,0 +1,40 @@ +#region Copyright (C) 2019-2020 Dylech30th. All rights reserved. + +// Pixeval - A Strong, Fast and Flexible Pixiv Client +// Copyright (C) 2019-2020 Dylech30th +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as +// published by the Free Software Foundation, either version 3 of the +// License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see . + +#endregion + +using System.Collections.Generic; +using Newtonsoft.Json; + +namespace Pixeval.Data.Web.Response +{ + public class AutoCompletionResponse + { + [JsonProperty("tags")] + public List Tags { get; set; } + + public class Tag + { + [JsonProperty("name")] + public string Name { get; set; } + + [JsonProperty("translated_name")] + public string TranslatedName { get; set; } + } + } +} \ No newline at end of file diff --git a/Pixeval/Data/Web/Response/DnsResolveResponse.cs b/Pixeval/Data/Web/Response/DnsResolveResponse.cs new file mode 100644 index 00000000..86bf24b9 --- /dev/null +++ b/Pixeval/Data/Web/Response/DnsResolveResponse.cs @@ -0,0 +1,76 @@ +#region Copyright (C) 2019-2020 Dylech30th. All rights reserved. + +// Pixeval - A Strong, Fast and Flexible Pixiv Client +// Copyright (C) 2019-2020 Dylech30th +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as +// published by the Free Software Foundation, either version 3 of the +// License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see . + +#endregion + +using System.Collections.Generic; +using Newtonsoft.Json; + +namespace Pixeval.Data.Web.Response +{ + public class DnsResolveResponse + { + [JsonProperty("Status")] + public long Status { get; set; } + + [JsonProperty("TC")] + public bool Tc { get; set; } + + [JsonProperty("RD")] + public bool Rd { get; set; } + + [JsonProperty("RA")] + public bool Ra { get; set; } + + [JsonProperty("AD")] + public bool Ad { get; set; } + + [JsonProperty("CD")] + public bool Cd { get; set; } + + [JsonProperty("Question")] + public List Questions { get; set; } + + [JsonProperty("Answer")] + public List Answers { get; set; } + + public class Answer + { + [JsonProperty("name")] + public string Name { get; set; } + + [JsonProperty("type")] + public long Type { get; set; } + + [JsonProperty("TTL")] + public long Ttl { get; set; } + + [JsonProperty("data")] + public string Data { get; set; } + } + + public class Question + { + [JsonProperty("name")] + public string Name { get; set; } + + [JsonProperty("type")] + public long Type { get; set; } + } + } +} \ No newline at end of file diff --git a/Pixeval/Data/Web/Response/FollowingResponse.cs b/Pixeval/Data/Web/Response/FollowingResponse.cs new file mode 100644 index 00000000..ab350afb --- /dev/null +++ b/Pixeval/Data/Web/Response/FollowingResponse.cs @@ -0,0 +1,263 @@ +#region Copyright (C) 2019-2020 Dylech30th. All rights reserved. + +// Pixeval - A Strong, Fast and Flexible Pixiv Client +// Copyright (C) 2019-2020 Dylech30th +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as +// published by the Free Software Foundation, either version 3 of the +// License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see . + +#endregion + +using System; +using System.Collections.Generic; +using Newtonsoft.Json; + +namespace Pixeval.Data.Web.Response +{ + public class FollowingResponse + { + [JsonProperty("user_previews")] + public List UserPreviews { get; set; } + + [JsonProperty("next_url")] + public string NextUrl { get; set; } + + public class UserPreview + { + [JsonProperty("user")] + public User User { get; set; } + + [JsonProperty("illusts")] + public List Illusts { get; set; } + + [JsonProperty("novels")] + public List Novels { get; set; } + + [JsonProperty("is_muted")] + public bool IsMuted { get; set; } + } + + public class Illust + { + [JsonProperty("id")] + public long Id { get; set; } + + [JsonProperty("title")] + public string Title { get; set; } + + [JsonProperty("type")] + public string Type { get; set; } + + [JsonProperty("image_urls")] + public ImageUrls ImageUrls { get; set; } + + [JsonProperty("caption")] + public string Caption { get; set; } + + [JsonProperty("restrict")] + public long Restrict { get; set; } + + [JsonProperty("user")] + public User User { get; set; } + + [JsonProperty("tags")] + public List Tags { get; set; } + + [JsonProperty("tools")] + public List Tools { get; set; } + + [JsonProperty("create_date")] + public DateTimeOffset CreateDate { get; set; } + + [JsonProperty("page_count")] + public long PageCount { get; set; } + + [JsonProperty("width")] + public long Width { get; set; } + + [JsonProperty("height")] + public long Height { get; set; } + + [JsonProperty("sanity_level")] + public long SanityLevel { get; set; } + + [JsonProperty("x_restrict")] + public long XRestrict { get; set; } + + [JsonProperty("series")] + public Series Series { get; set; } + + [JsonProperty("meta_single_page")] + public MetaSinglePage MetaSinglePage { get; set; } + + [JsonProperty("meta_pages")] + public List MetaPages { get; set; } + + [JsonProperty("total_view")] + public long TotalView { get; set; } + + [JsonProperty("total_bookmarks")] + public long TotalBookmarks { get; set; } + + [JsonProperty("is_bookmarked")] + public bool IsBookmarked { get; set; } + + [JsonProperty("visible")] + public bool Visible { get; set; } + + [JsonProperty("is_muted")] + public bool IsMuted { get; set; } + } + + public class ImageUrls + { + [JsonProperty("square_medium")] + public string SquareMedium { get; set; } + + [JsonProperty("medium")] + public string Medium { get; set; } + + [JsonProperty("large")] + public string Large { get; set; } + + [JsonProperty("original", NullValueHandling = NullValueHandling.Ignore)] + public Uri Original { get; set; } + } + + public class MetaPage + { + [JsonProperty("image_urls")] + public ImageUrls ImageUrls { get; set; } + } + + public class MetaSinglePage + { + [JsonProperty("original_image_url", NullValueHandling = NullValueHandling.Ignore)] + public Uri OriginalImageUrl { get; set; } + } + + public class Series + { + [JsonProperty("id", NullValueHandling = NullValueHandling.Ignore)] + public long? Id { get; set; } + + [JsonProperty("title", NullValueHandling = NullValueHandling.Ignore)] + public string Title { get; set; } + } + + public class IllustTag + { + [JsonProperty("name")] + public string Name { get; set; } + } + + public class User + { + [JsonProperty("id")] + public long Id { get; set; } + + [JsonProperty("name")] + public string Name { get; set; } + + [JsonProperty("account")] + public string Account { get; set; } + + [JsonProperty("profile_image_urls")] + public ProfileImageUrls ProfileImageUrls { get; set; } + + [JsonProperty("is_followed")] + public bool IsFollowed { get; set; } + } + + public class ProfileImageUrls + { + [JsonProperty("medium")] + public string Medium { get; set; } + } + + public class Novel + { + [JsonProperty("id")] + public long Id { get; set; } + + [JsonProperty("title")] + public string Title { get; set; } + + [JsonProperty("caption")] + public string Caption { get; set; } + + [JsonProperty("restrict")] + public long Restrict { get; set; } + + [JsonProperty("x_restrict")] + public long XRestrict { get; set; } + + [JsonProperty("is_original")] + public bool IsOriginal { get; set; } + + [JsonProperty("image_urls")] + public ImageUrls ImageUrls { get; set; } + + [JsonProperty("create_date")] + public DateTimeOffset CreateDate { get; set; } + + [JsonProperty("tags")] + public List Tags { get; set; } + + [JsonProperty("page_count")] + public long PageCount { get; set; } + + [JsonProperty("text_length")] + public long TextLength { get; set; } + + [JsonProperty("user")] + public User User { get; set; } + + [JsonProperty("series")] + public Series Series { get; set; } + + [JsonProperty("is_bookmarked")] + public bool IsBookmarked { get; set; } + + [JsonProperty("total_bookmarks")] + public long TotalBookmarks { get; set; } + + [JsonProperty("total_view")] + public long TotalView { get; set; } + + [JsonProperty("visible")] + public bool Visible { get; set; } + + [JsonProperty("total_comments")] + public long TotalComments { get; set; } + + [JsonProperty("is_muted")] + public bool IsMuted { get; set; } + + [JsonProperty("is_mypixiv_only")] + public bool IsMypixivOnly { get; set; } + + [JsonProperty("is_x_restricted")] + public bool IsXRestricted { get; set; } + } + + public class NovelTag + { + [JsonProperty("name")] + public string Name { get; set; } + + [JsonProperty("added_by_uploaded_user")] + public bool AddedByUploadedUser { get; set; } + } + } +} \ No newline at end of file diff --git a/Pixeval/Data/Web/Response/GalleryResponse.cs b/Pixeval/Data/Web/Response/GalleryResponse.cs new file mode 100644 index 00000000..1d02eafe --- /dev/null +++ b/Pixeval/Data/Web/Response/GalleryResponse.cs @@ -0,0 +1,207 @@ +#region Copyright (C) 2019-2020 Dylech30th. All rights reserved. + +// Pixeval - A Strong, Fast and Flexible Pixiv Client +// Copyright (C) 2019-2020 Dylech30th +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as +// published by the Free Software Foundation, either version 3 of the +// License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see . + +#endregion + +using System; +using System.Collections.Generic; +using System.Linq; +using Newtonsoft.Json; +using Pixeval.Data.ViewModel; +using Pixeval.Objects.Generic; +using Pixeval.Objects.Primitive; + +namespace Pixeval.Data.Web.Response +{ + public class GalleryResponse + { + [JsonProperty("illusts")] + public List Illusts { get; set; } + + [JsonProperty("next_url")] + public string NextUrl { get; set; } + + public class Illust : IParser + { + [JsonProperty("id")] + public long Id { get; set; } + + [JsonProperty("title")] + public string Title { get; set; } + + [JsonProperty("type")] + public string Type { get; set; } + + [JsonProperty("image_urls")] + public ImageUrls ImageUrls { get; set; } + + [JsonProperty("caption")] + public string Caption { get; set; } + + [JsonProperty("restrict")] + public long Restrict { get; set; } + + [JsonProperty("user")] + public User User { get; set; } + + [JsonProperty("tags")] + public List Tags { get; set; } + + [JsonProperty("tools")] + public List Tools { get; set; } + + [JsonProperty("create_date")] + public DateTimeOffset CreateDate { get; set; } + + [JsonProperty("page_count")] + public long PageCount { get; set; } + + [JsonProperty("width")] + public long Width { get; set; } + + [JsonProperty("height")] + public long Height { get; set; } + + [JsonProperty("sanity_level")] + public long SanityLevel { get; set; } + + [JsonProperty("x_restrict")] + public long XRestrict { get; set; } + + [JsonProperty("meta_single_page")] + public MetaSinglePage MetaSinglePage { get; set; } + + [JsonProperty("meta_pages")] + public List MetaPages { get; set; } + + [JsonProperty("total_view")] + public long TotalView { get; set; } + + [JsonProperty("total_bookmarks")] + public long TotalBookmarks { get; set; } + + [JsonProperty("is_bookmarked")] + public bool IsBookmarked { get; set; } + + [JsonProperty("visible")] + public bool Visible { get; set; } + + [JsonProperty("is_muted")] + public bool IsMuted { get; set; } + + public Illustration Parse() + { + return new Illustration + { + Bookmark = (int) TotalBookmarks, + Id = Id.ToString(), + IsLiked = IsBookmarked, + IsManga = PageCount != 1, + IsUgoira = Type == "ugoira", + Origin = MetaSinglePage.OriginalImageUrl, + Large = ImageUrls.Large, + Tags = Tags.Select(t => new ViewModel.Tag { Name = t.Name, TranslatedName = t.TranslatedName }), + Thumbnail = ImageUrls.Medium.IsNullOrEmpty() ? ImageUrls.SquareMedium : ImageUrls.Medium, + Title = Title, + UserId = User.Id.ToString(), + UserName = User.Name, + Resolution = $"{Width}x{Height}", + ViewCount = (int) TotalView, + PublishDate = CreateDate + }.Apply(i => + { + if (i.IsManga) + { + i.MangaMetadata = MetaPages.Select(p => + { + var page = (Illustration) i.Clone(); + page.Thumbnail = p.ImageUrls.Medium; + page.Origin = p.ImageUrls.Original; + page.Large = p.ImageUrls.Large; + return page; + }).ToArray(); + foreach (var illustration in i.MangaMetadata) + { + illustration.MangaMetadata = i.MangaMetadata; + } + } + }); + } + } + + public class ImageUrls + { + [JsonProperty("square_medium")] + public string SquareMedium { get; set; } + + [JsonProperty("medium")] + public string Medium { get; set; } + + [JsonProperty("large")] + public string Large { get; set; } + + [JsonProperty("original", NullValueHandling = NullValueHandling.Ignore)] + public string Original { get; set; } + } + + public class MetaPage + { + [JsonProperty("image_urls")] + public ImageUrls ImageUrls { get; set; } + } + + public class MetaSinglePage + { + [JsonProperty("original_image_url", NullValueHandling = NullValueHandling.Ignore)] + public string OriginalImageUrl { get; set; } + } + + public class Tag + { + [JsonProperty("name")] + public string Name { get; set; } + + [JsonProperty("translated_name")] + public string TranslatedName { get; set; } + } + + public class User + { + [JsonProperty("id")] + public long Id { get; set; } + + [JsonProperty("name")] + public string Name { get; set; } + + [JsonProperty("account")] + public string Account { get; set; } + + [JsonProperty("profile_image_urls")] + public ProfileImageUrls ProfileImageUrls { get; set; } + + [JsonProperty("is_followed")] + public bool IsFollowed { get; set; } + } + + public class ProfileImageUrls + { + [JsonProperty("medium")] + public string Medium { get; set; } + } + } +} \ No newline at end of file diff --git a/Pixeval/Data/Web/Response/QueryWorksResponse.cs b/Pixeval/Data/Web/Response/QueryWorksResponse.cs new file mode 100644 index 00000000..df1d2677 --- /dev/null +++ b/Pixeval/Data/Web/Response/QueryWorksResponse.cs @@ -0,0 +1,206 @@ +#region Copyright (C) 2019-2020 Dylech30th. All rights reserved. + +// Pixeval - A Strong, Fast and Flexible Pixiv Client +// Copyright (C) 2019-2020 Dylech30th +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as +// published by the Free Software Foundation, either version 3 of the +// License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see . + +#endregion + +using System; +using System.Collections.Generic; +using System.Linq; +using Newtonsoft.Json; +using Pixeval.Data.ViewModel; +using Pixeval.Objects.Generic; + +namespace Pixeval.Data.Web.Response +{ + public class QueryWorksResponse + { + [JsonProperty("illusts")] + public List Illusts { get; set; } + + [JsonProperty("next_url")] + public string NextUrl { get; set; } + + public class Illust : IParser + { + [JsonProperty("id")] + public long Id { get; set; } + + [JsonProperty("title")] + public string Title { get; set; } + + [JsonProperty("type")] + public string Type { get; set; } + + [JsonProperty("image_urls")] + public ImageUrls ImageUrls { get; set; } + + [JsonProperty("caption")] + public string Caption { get; set; } + + [JsonProperty("restrict")] + public long Restrict { get; set; } + + [JsonProperty("user")] + public User User { get; set; } + + [JsonProperty("tags")] + public List Tags { get; set; } + + [JsonProperty("tools")] + public List Tools { get; set; } + + [JsonProperty("create_date")] + public DateTimeOffset CreateDate { get; set; } + + [JsonProperty("page_count")] + public long PageCount { get; set; } + + [JsonProperty("width")] + public long Width { get; set; } + + [JsonProperty("height")] + public long Height { get; set; } + + [JsonProperty("sanity_level")] + public long SanityLevel { get; set; } + + [JsonProperty("x_restrict")] + public long XRestrict { get; set; } + + [JsonProperty("meta_single_page")] + public MetaSinglePage MetaSinglePage { get; set; } + + [JsonProperty("meta_pages")] + public List MetaPages { get; set; } + + [JsonProperty("total_view")] + public long TotalView { get; set; } + + [JsonProperty("total_bookmarks")] + public long TotalBookmarks { get; set; } + + [JsonProperty("is_bookmarked")] + public bool IsBookmarked { get; set; } + + [JsonProperty("visible")] + public bool Visible { get; set; } + + [JsonProperty("is_muted")] + public bool IsMuted { get; set; } + + public Illustration Parse() + { + return new Illustration + { + Bookmark = (int) TotalBookmarks, + Id = Id.ToString(), + IsLiked = IsBookmarked, + IsUgoira = Type == "ugoira", + IsManga = !MetaPages.IsNullOrEmpty(), + Origin = MetaSinglePage.OriginalImageUrl ?? ImageUrls.Large, + Large = ImageUrls.Large, + Tags = Tags.Select(t => new ViewModel.Tag { Name = t.Name, TranslatedName = t.TranslatedName }), + Thumbnail = ImageUrls.Medium ?? ImageUrls.SquareMedium, + Title = Title, + UserId = User.Id.ToString(), + UserName = User.Name, + Resolution = $"{Width}x{Height}", + ViewCount = (int) TotalView, + PublishDate = CreateDate + }.Apply(i => + { + if (i.IsManga) + { + i.MangaMetadata = MetaPages.Select(p => + { + var page = (Illustration) i.Clone(); + page.Thumbnail = p.ImageUrls.Medium ?? p.ImageUrls.SquareMedium; + page.Origin = p.ImageUrls.Original; + page.Large = p.ImageUrls.Large; + return page; + }).ToArray(); + foreach (var illustration in i.MangaMetadata) + { + illustration.MangaMetadata = i.MangaMetadata; + } + } + }); + } + } + + public class ImageUrls + { + [JsonProperty("square_medium")] + public string SquareMedium { get; set; } + + [JsonProperty("medium")] + public string Medium { get; set; } + + [JsonProperty("large")] + public string Large { get; set; } + + [JsonProperty("original", NullValueHandling = NullValueHandling.Ignore)] + public string Original { get; set; } + } + + public class MetaPage + { + [JsonProperty("image_urls")] + public ImageUrls ImageUrls { get; set; } + } + + public class MetaSinglePage + { + [JsonProperty("original_image_url", NullValueHandling = NullValueHandling.Ignore)] + public string OriginalImageUrl { get; set; } + } + + public class Tag + { + [JsonProperty("name")] + public string Name { get; set; } + + [JsonProperty("translated_name")] + public string TranslatedName { get; set; } + } + + public class User + { + [JsonProperty("id")] + public long Id { get; set; } + + [JsonProperty("name")] + public string Name { get; set; } + + [JsonProperty("account")] + public string Account { get; set; } + + [JsonProperty("profile_image_urls")] + public ProfileImageUrls ProfileImageUrls { get; set; } + + [JsonProperty("is_followed")] + public bool IsFollowed { get; set; } + } + + public class ProfileImageUrls + { + [JsonProperty("medium")] + public string Medium { get; set; } + } + } +} \ No newline at end of file diff --git a/Pixeval/Data/Web/Response/RankingResponse.cs b/Pixeval/Data/Web/Response/RankingResponse.cs new file mode 100644 index 00000000..5455db09 --- /dev/null +++ b/Pixeval/Data/Web/Response/RankingResponse.cs @@ -0,0 +1,207 @@ +#region Copyright (C) 2019-2020 Dylech30th. All rights reserved. + +// Pixeval - A Strong, Fast and Flexible Pixiv Client +// Copyright (C) 2019-2020 Dylech30th +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as +// published by the Free Software Foundation, either version 3 of the +// License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see . + +#endregion + +using System; +using System.Collections.Generic; +using System.Linq; +using Newtonsoft.Json; +using Pixeval.Data.ViewModel; +using Pixeval.Objects.Generic; +using Pixeval.Objects.Primitive; + +namespace Pixeval.Data.Web.Response +{ + public class RankingResponse + { + [JsonProperty("illusts")] + public List Illusts { get; set; } + + [JsonProperty("next_url")] + public string NextUrl { get; set; } + + public class Illust : IParser + { + [JsonProperty("id")] + public long Id { get; set; } + + [JsonProperty("title")] + public string Title { get; set; } + + [JsonProperty("type")] + public string Type { get; set; } + + [JsonProperty("image_urls")] + public ImageUrls ImageUrls { get; set; } + + [JsonProperty("caption")] + public string Caption { get; set; } + + [JsonProperty("restrict")] + public long Restrict { get; set; } + + [JsonProperty("user")] + public User User { get; set; } + + [JsonProperty("tags")] + public List Tags { get; set; } + + [JsonProperty("tools")] + public List Tools { get; set; } + + [JsonProperty("create_date")] + public DateTimeOffset CreateDate { get; set; } + + [JsonProperty("page_count")] + public long PageCount { get; set; } + + [JsonProperty("width")] + public long Width { get; set; } + + [JsonProperty("height")] + public long Height { get; set; } + + [JsonProperty("sanity_level")] + public long SanityLevel { get; set; } + + [JsonProperty("x_restrict")] + public long XRestrict { get; set; } + + [JsonProperty("meta_single_page")] + public MetaSinglePage MetaSinglePage { get; set; } + + [JsonProperty("meta_pages")] + public List MetaPages { get; set; } + + [JsonProperty("total_view")] + public long TotalView { get; set; } + + [JsonProperty("total_bookmarks")] + public long TotalBookmarks { get; set; } + + [JsonProperty("is_bookmarked")] + public bool IsBookmarked { get; set; } + + [JsonProperty("visible")] + public bool Visible { get; set; } + + [JsonProperty("is_muted")] + public bool IsMuted { get; set; } + + public Illustration Parse() + { + return new Illustration + { + Bookmark = (int) TotalBookmarks, + Id = Id.ToString(), + IsLiked = IsBookmarked, + IsManga = PageCount != 1, + IsUgoira = Type == "ugoira", + Origin = MetaSinglePage.OriginalImageUrl, + Large = ImageUrls.Large, + Tags = Tags.Select(t => new ViewModel.Tag { Name = t.Name, TranslatedName = t.TranslatedName }), + Thumbnail = ImageUrls.Medium.IsNullOrEmpty() ? ImageUrls.SquareMedium : ImageUrls.Medium, + Title = Title, + UserId = User.Id.ToString(), + UserName = User.Name, + Resolution = $"{Width}x{Height}", + ViewCount = (int) TotalView, + PublishDate = CreateDate + }.Apply(i => + { + if (i.IsManga) + { + i.MangaMetadata = MetaPages.Select(p => + { + var page = (Illustration) i.Clone(); + page.Thumbnail = p.ImageUrls.Medium; + page.Origin = p.ImageUrls.Original; + page.Large = p.ImageUrls.Large; + return page; + }).ToArray(); + foreach (var illustration in i.MangaMetadata) + { + illustration.MangaMetadata = i.MangaMetadata; + } + } + }); + } + } + + public class ImageUrls + { + [JsonProperty("square_medium")] + public string SquareMedium { get; set; } + + [JsonProperty("medium")] + public string Medium { get; set; } + + [JsonProperty("large")] + public string Large { get; set; } + + [JsonProperty("original", NullValueHandling = NullValueHandling.Ignore)] + public string Original { get; set; } + } + + public class MetaPage + { + [JsonProperty("image_urls")] + public ImageUrls ImageUrls { get; set; } + } + + public class MetaSinglePage + { + [JsonProperty("original_image_url", NullValueHandling = NullValueHandling.Ignore)] + public string OriginalImageUrl { get; set; } + } + + public class Tag + { + [JsonProperty("name")] + public string Name { get; set; } + + [JsonProperty("translated_name")] + public string TranslatedName { get; set; } + } + + public class User + { + [JsonProperty("id")] + public long Id { get; set; } + + [JsonProperty("name")] + public string Name { get; set; } + + [JsonProperty("account")] + public string Account { get; set; } + + [JsonProperty("profile_image_urls")] + public ProfileImageUrls ProfileImageUrls { get; set; } + + [JsonProperty("is_followed")] + public bool IsFollowed { get; set; } + } + + public class ProfileImageUrls + { + [JsonProperty("medium")] + public string Medium { get; set; } + } + } +} \ No newline at end of file diff --git a/Pixeval/Data/Web/Response/RecommendIllustratorResponse.cs b/Pixeval/Data/Web/Response/RecommendIllustratorResponse.cs new file mode 100644 index 00000000..246d8b2e --- /dev/null +++ b/Pixeval/Data/Web/Response/RecommendIllustratorResponse.cs @@ -0,0 +1,185 @@ +#region Copyright (C) 2019-2020 Dylech30th. All rights reserved. + +// Pixeval - A Strong, Fast and Flexible Pixiv Client +// Copyright (C) 2019-2020 Dylech30th +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as +// published by the Free Software Foundation, either version 3 of the +// License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see . + +#endregion + +using System; +using System.Linq; +using System.Text.RegularExpressions; +using Newtonsoft.Json; + +namespace Pixeval.Data.Web.Response +{ + public class RecommendIllustratorResponse + { + [JsonProperty("user_previews")] + public UserPreview[] UserPreviews { get; set; } + + [JsonProperty("next_url")] + public string NextUrl { get; set; } + + public class UserPreview : IParser + { + [JsonProperty("user")] + public User User { get; set; } + + [JsonProperty("illusts")] + public Illust[] Illusts { get; set; } + + [JsonProperty("is_muted")] + public bool IsMuted { get; set; } + + public ViewModel.User Parse() + { + return new ViewModel.User + { + Avatar = Regex.Replace(User.ProfileImageUrls.Medium, "_170\\.", "_50."), + Id = User.Id.ToString(), + Name = User.Name, + Thumbnails = Illusts.Select(i => i.ImageUrls.SquareMedium).ToArray() + }; + } + } + + public class Illust + { + [JsonProperty("id")] + public long Id { get; set; } + + [JsonProperty("title")] + public string Title { get; set; } + + [JsonProperty("type")] + public string Type { get; set; } + + [JsonProperty("image_urls")] + public ImageUrls ImageUrls { get; set; } + + [JsonProperty("caption")] + public string Caption { get; set; } + + [JsonProperty("restrict")] + public long Restrict { get; set; } + + [JsonProperty("user")] + public User User { get; set; } + + [JsonProperty("tags")] + public Tag[] Tags { get; set; } + + [JsonProperty("tools")] + public string[] Tools { get; set; } + + [JsonProperty("create_date")] + public DateTimeOffset CreateDate { get; set; } + + [JsonProperty("page_count")] + public long PageCount { get; set; } + + [JsonProperty("width")] + public long Width { get; set; } + + [JsonProperty("height")] + public long Height { get; set; } + + [JsonProperty("sanity_level")] + public long SanityLevel { get; set; } + + [JsonProperty("x_restrict")] + public long XRestrict { get; set; } + + [JsonProperty("meta_single_page")] + public MetaSinglePage MetaSinglePage { get; set; } + + [JsonProperty("meta_pages")] + public MetaPage[] MetaPages { get; set; } + + [JsonProperty("total_view")] + public long TotalView { get; set; } + + [JsonProperty("total_bookmarks")] + public long TotalBookmarks { get; set; } + + [JsonProperty("is_bookmarked")] + public bool IsBookmarked { get; set; } + + [JsonProperty("visible")] + public bool Visible { get; set; } + + [JsonProperty("is_muted")] + public bool IsMuted { get; set; } + } + + public class ImageUrls + { + [JsonProperty("square_medium")] + public string SquareMedium { get; set; } + + [JsonProperty("medium")] + public string Medium { get; set; } + + [JsonProperty("large")] + public string Large { get; set; } + + [JsonProperty("original", NullValueHandling = NullValueHandling.Ignore)] + public string Original { get; set; } + } + + public class MetaPage + { + [JsonProperty("image_urls")] + public ImageUrls ImageUrls { get; set; } + } + + public class MetaSinglePage + { + [JsonProperty("original_image_url", NullValueHandling = NullValueHandling.Ignore)] + public string OriginalImageUrl { get; set; } + } + + public class Tag + { + [JsonProperty("name")] + public string Name { get; set; } + } + + public class User + { + [JsonProperty("id")] + public long Id { get; set; } + + [JsonProperty("name")] + public string Name { get; set; } + + [JsonProperty("account")] + public string Account { get; set; } + + [JsonProperty("profile_image_urls")] + public ProfileImageUrls ProfileImageUrls { get; set; } + + [JsonProperty("is_followed")] + public bool IsFollowed { get; set; } + } + + public class ProfileImageUrls + { + [JsonProperty("medium")] + public string Medium { get; set; } + } + } +} \ No newline at end of file diff --git a/Pixeval/Data/Web/Response/RecommendResponse.cs b/Pixeval/Data/Web/Response/RecommendResponse.cs new file mode 100644 index 00000000..d2b9f6bf --- /dev/null +++ b/Pixeval/Data/Web/Response/RecommendResponse.cs @@ -0,0 +1,210 @@ +#region Copyright (C) 2019-2020 Dylech30th. All rights reserved. + +// Pixeval - A Strong, Fast and Flexible Pixiv Client +// Copyright (C) 2019-2020 Dylech30th +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as +// published by the Free Software Foundation, either version 3 of the +// License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see . + +#endregion + +using System; +using System.Collections.Generic; +using System.Linq; +using Newtonsoft.Json; +using Pixeval.Data.ViewModel; +using Pixeval.Objects.Generic; +using Pixeval.Objects.Primitive; + +namespace Pixeval.Data.Web.Response +{ + public class RecommendResponse + { + [JsonProperty("illusts")] + public List Illusts { get; set; } + + [JsonProperty("contest_exists")] + public bool ContestExists { get; set; } + + [JsonProperty("next_url")] + public string NextUrl { get; set; } + + public class Illust : IParser + { + [JsonProperty("id")] + public long Id { get; set; } + + [JsonProperty("title")] + public string Title { get; set; } + + [JsonProperty("type")] + public string Type { get; set; } + + [JsonProperty("image_urls")] + public ImageUrls ImageUrls { get; set; } + + [JsonProperty("caption")] + public string Caption { get; set; } + + [JsonProperty("restrict")] + public long Restrict { get; set; } + + [JsonProperty("user")] + public User User { get; set; } + + [JsonProperty("tags")] + public List Tags { get; set; } + + [JsonProperty("tools")] + public List Tools { get; set; } + + [JsonProperty("create_date")] + public DateTimeOffset CreateDate { get; set; } + + [JsonProperty("page_count")] + public long PageCount { get; set; } + + [JsonProperty("width")] + public long Width { get; set; } + + [JsonProperty("height")] + public long Height { get; set; } + + [JsonProperty("sanity_level")] + public long SanityLevel { get; set; } + + [JsonProperty("x_restrict")] + public long XRestrict { get; set; } + + [JsonProperty("meta_single_page")] + public MetaSinglePage MetaSinglePage { get; set; } + + [JsonProperty("meta_pages")] + public List MetaPages { get; set; } + + [JsonProperty("total_view")] + public long TotalView { get; set; } + + [JsonProperty("total_bookmarks")] + public long TotalBookmarks { get; set; } + + [JsonProperty("is_bookmarked")] + public bool IsBookmarked { get; set; } + + [JsonProperty("visible")] + public bool Visible { get; set; } + + [JsonProperty("is_muted")] + public bool IsMuted { get; set; } + + public Illustration Parse() + { + return new Illustration + { + Bookmark = (int) TotalBookmarks, + Id = Id.ToString(), + IsLiked = IsBookmarked, + IsManga = PageCount != 1, + IsUgoira = Type == "ugoira", + Origin = MetaSinglePage.OriginalImageUrl.IsNullOrEmpty() ? MetaPages[0].ImageUrls.Original : MetaSinglePage.OriginalImageUrl, + Large = ImageUrls.Large, + Tags = Tags.Select(t => new ViewModel.Tag { Name = t.Name, TranslatedName = t.TranslatedName }), + Thumbnail = ImageUrls.Medium, + Title = Title, + UserId = User.Id.ToString(), + UserName = User.Name, + Resolution = $"{Width}x{Height}", + ViewCount = (int) TotalView, + PublishDate = CreateDate + }.Apply(i => + { + if (i.IsManga) + { + i.MangaMetadata = MetaPages.Select(p => + { + var page = (Illustration) i.Clone(); + page.Thumbnail = p.ImageUrls.Medium; + page.Origin = p.ImageUrls.Original; + page.Large = p.ImageUrls.Large; + return page; + }).ToArray(); + foreach (var illustration in i.MangaMetadata) + { + illustration.MangaMetadata = i.MangaMetadata; + } + } + }); + } + } + + public class ImageUrls + { + [JsonProperty("square_medium")] + public string SquareMedium { get; set; } + + [JsonProperty("medium")] + public string Medium { get; set; } + + [JsonProperty("large")] + public string Large { get; set; } + + [JsonProperty("original", NullValueHandling = NullValueHandling.Ignore)] + public string Original { get; set; } + } + + public class MetaPage + { + [JsonProperty("image_urls")] + public ImageUrls ImageUrls { get; set; } + } + + public class MetaSinglePage + { + [JsonProperty("original_image_url", NullValueHandling = NullValueHandling.Ignore)] + public string OriginalImageUrl { get; set; } + } + + public class Tag + { + [JsonProperty("name")] + public string Name { get; set; } + + [JsonProperty("translated_name")] + public string TranslatedName { get; set; } + } + + public class User + { + [JsonProperty("id")] + public long Id { get; set; } + + [JsonProperty("name")] + public string Name { get; set; } + + [JsonProperty("account")] + public string Account { get; set; } + + [JsonProperty("profile_image_urls")] + public ProfileImageUrls ProfileImageUrls { get; set; } + + [JsonProperty("is_followed")] + public bool IsFollowed { get; set; } + } + + public class ProfileImageUrls + { + [JsonProperty("medium")] + public string Medium { get; set; } + } + } +} \ No newline at end of file diff --git a/Pixeval/Data/Web/Response/SingleWorkResponse.cs b/Pixeval/Data/Web/Response/SingleWorkResponse.cs new file mode 100644 index 00000000..430197cb --- /dev/null +++ b/Pixeval/Data/Web/Response/SingleWorkResponse.cs @@ -0,0 +1,164 @@ +#region Copyright (C) 2019-2020 Dylech30th. All rights reserved. + +// Pixeval - A Strong, Fast and Flexible Pixiv Client +// Copyright (C) 2019-2020 Dylech30th +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as +// published by the Free Software Foundation, either version 3 of the +// License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see . + +#endregion + +using System; +using System.Collections.Generic; +using Newtonsoft.Json; + +namespace Pixeval.Data.Web.Response +{ + public class SingleWorkResponse + { + [JsonProperty("illust")] + public Illust IllustInfo { get; set; } + + public class Illust + { + [JsonProperty("id")] + public long Id { get; set; } + + [JsonProperty("title")] + public string Title { get; set; } + + [JsonProperty("type")] + public string Type { get; set; } + + [JsonProperty("image_urls")] + public ImageUrls ImageUrls { get; set; } + + [JsonProperty("caption")] + public string Caption { get; set; } + + [JsonProperty("restrict")] + public long Restrict { get; set; } + + [JsonProperty("user")] + public User User { get; set; } + + [JsonProperty("tags")] + public List Tags { get; set; } + + [JsonProperty("tools")] + public List Tools { get; set; } + + [JsonProperty("create_date")] + public DateTimeOffset CreateDate { get; set; } + + [JsonProperty("page_count")] + public long PageCount { get; set; } + + [JsonProperty("width")] + public long Width { get; set; } + + [JsonProperty("height")] + public long Height { get; set; } + + [JsonProperty("sanity_level")] + public long SanityLevel { get; set; } + + [JsonProperty("x_restrict")] + public long XRestrict { get; set; } + + [JsonProperty("meta_single_page")] + public MetaSinglePage MetaSinglePage { get; set; } + + [JsonProperty("meta_pages")] + public List MetaPages { get; set; } + + [JsonProperty("total_view")] + public long TotalView { get; set; } + + [JsonProperty("total_bookmarks")] + public long TotalBookmarks { get; set; } + + [JsonProperty("is_bookmarked")] + public bool IsBookmarked { get; set; } + + [JsonProperty("visible")] + public bool Visible { get; set; } + + [JsonProperty("is_muted")] + public bool IsMuted { get; set; } + + [JsonProperty("total_comments")] + public long TotalComments { get; set; } + } + + public class ImageUrls + { + [JsonProperty("square_medium")] + public string SquareMedium { get; set; } + + [JsonProperty("medium")] + public string Medium { get; set; } + + [JsonProperty("large")] + public string Large { get; set; } + + [JsonProperty("original", NullValueHandling = NullValueHandling.Ignore)] + public string Original { get; set; } + } + + public class MetaPage + { + [JsonProperty("image_urls")] + public ImageUrls ImageUrls { get; set; } + } + + public class MetaSinglePage + { + [JsonProperty("original_image_url")] + public string OriginalImageUrl { get; set; } + } + + public class Tag + { + [JsonProperty("name")] + public string Name { get; set; } + + [JsonProperty("translated_name")] + public string TranslatedName { get; set; } + } + + public class User + { + [JsonProperty("id")] + public long Id { get; set; } + + [JsonProperty("name")] + public string Name { get; set; } + + [JsonProperty("account")] + public string Account { get; set; } + + [JsonProperty("profile_image_urls")] + public ProfileImageUrls ProfileImageUrls { get; set; } + + [JsonProperty("is_followed")] + public bool IsFollowed { get; set; } + } + + public class ProfileImageUrls + { + [JsonProperty("medium")] + public string Medium { get; set; } + } + } +} \ No newline at end of file diff --git a/Pixeval/Data/Web/Response/SpotlightArticleResponse.cs b/Pixeval/Data/Web/Response/SpotlightArticleResponse.cs new file mode 100644 index 00000000..7b406d29 --- /dev/null +++ b/Pixeval/Data/Web/Response/SpotlightArticleResponse.cs @@ -0,0 +1,49 @@ +#region Copyright (C) 2019-2020 Dylech30th. All rights reserved. + +// Pixeval - A Strong, Fast and Flexible Pixiv Client +// Copyright (C) 2019-2020 Dylech30th +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as +// published by the Free Software Foundation, either version 3 of the +// License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see . + +#endregion + +using System.Collections.Generic; +using Newtonsoft.Json; + +namespace Pixeval.Data.Web.Response +{ + public class SpotlightArticleResponse + { + [JsonProperty("body")] + public List BodyList { get; set; } + + public class Body + { + [JsonProperty("id")] + public string Id { get; set; } + + [JsonProperty("title")] + public string Title { get; set; } + + [JsonProperty("illusts")] + public List Illusts { get; set; } + } + + public class Illust + { + [JsonProperty("illust_id")] + public long IllustId { get; set; } + } + } +} \ No newline at end of file diff --git a/Pixeval/Data/Web/Response/SpotlightResponse.cs b/Pixeval/Data/Web/Response/SpotlightResponse.cs new file mode 100644 index 00000000..060106b6 --- /dev/null +++ b/Pixeval/Data/Web/Response/SpotlightResponse.cs @@ -0,0 +1,35 @@ +#region Copyright (C) 2019-2020 Dylech30th. All rights reserved. + +// Pixeval - A Strong, Fast and Flexible Pixiv Client +// Copyright (C) 2019-2020 Dylech30th +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as +// published by the Free Software Foundation, either version 3 of the +// License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see . + +#endregion + +using System.Collections.Generic; +using Newtonsoft.Json; +using Pixeval.Data.ViewModel; + +namespace Pixeval.Data.Web.Response +{ + public class SpotlightResponse + { + [JsonProperty("spotlight_articles")] + public List SpotlightArticles { get; set; } + + [JsonProperty("next_url")] + public string NextUrl { get; set; } + } +} \ No newline at end of file diff --git a/Pixeval/Data/Web/Response/TokenResponse.cs b/Pixeval/Data/Web/Response/TokenResponse.cs new file mode 100644 index 00000000..6686e504 --- /dev/null +++ b/Pixeval/Data/Web/Response/TokenResponse.cs @@ -0,0 +1,136 @@ +#region Copyright (C) 2019-2020 Dylech30th. All rights reserved. + +// Pixeval - A Strong, Fast and Flexible Pixiv Client +// Copyright (C) 2019-2020 Dylech30th +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as +// published by the Free Software Foundation, either version 3 of the +// License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see . + +#endregion + +using System; +using Newtonsoft.Json; +using Pixeval.Objects.Exceptions; +using Pixeval.Objects.Primitive; + +namespace Pixeval.Data.Web.Response +{ + public class TokenResponse + { + [JsonProperty("response")] + public Response ToResponse { get; set; } + + public override string ToString() + { + return this.ToJson(); + } + + public class Response + { + [JsonProperty("access_token")] + public string AccessToken { get; set; } + + [JsonProperty("expires_in")] + public long ExpiresIn { get; set; } + + [JsonProperty("token_type")] + public string TokenType { get; set; } + + [JsonProperty("scope")] + public string Scope { get; set; } + + [JsonProperty("refresh_token")] + public string RefreshToken { get; set; } + + [JsonProperty("user")] + public User User { get; set; } + + [JsonProperty("device_token")] + public string DeviceToken { get; set; } + } + + public class User + { + [JsonProperty("profile_image_urls")] + public ProfileImageUrls ProfileImageUrls { get; set; } + + [JsonProperty("id")] + [JsonConverter(typeof(ParseStringConverter))] + public long Id { get; set; } + + [JsonProperty("name")] + public string Name { get; set; } + + [JsonProperty("account")] + public string Account { get; set; } + + [JsonProperty("mail_address")] + public string MailAddress { get; set; } + + [JsonProperty("is_premium")] + public bool IsPremium { get; set; } + + [JsonProperty("x_restrict")] + public long XRestrict { get; set; } + + [JsonProperty("is_mail_authorized")] + public bool IsMailAuthorized { get; set; } + } + + public class ProfileImageUrls + { + [JsonProperty("px_16x16")] + public string Px16X16 { get; set; } + + [JsonProperty("px_50x50")] + public string Px50X50 { get; set; } + + [JsonProperty("px_170x170")] + public string Px170X170 { get; set; } + } + + private class ParseStringConverter : JsonConverter + { + public override bool CanConvert(Type t) + { + return t == typeof(long) || t == typeof(long?); + } + + public override object ReadJson(JsonReader reader, Type t, object existingValue, JsonSerializer serializer) + { + if (reader.TokenType == JsonToken.Null) + { + return null; + } + var value = serializer.Deserialize(reader); + if (long.TryParse(value, out var l)) + { + return l; + } + throw new TypeMismatchException("Cannot unmarshal type long"); + } + + public override void WriteJson(JsonWriter writer, object untypedValue, JsonSerializer serializer) + { + if (untypedValue == null) + { + serializer.Serialize(writer, null); + return; + } + + var value = (long) untypedValue; + serializer.Serialize(writer, value.ToString()); + } + } + } +} \ No newline at end of file diff --git a/Pixeval/Data/Web/Response/TrendingTagResponse.cs b/Pixeval/Data/Web/Response/TrendingTagResponse.cs new file mode 100644 index 00000000..54bc1c65 --- /dev/null +++ b/Pixeval/Data/Web/Response/TrendingTagResponse.cs @@ -0,0 +1,173 @@ +#region Copyright (C) 2019-2020 Dylech30th. All rights reserved. + +// Pixeval - A Strong, Fast and Flexible Pixiv Client +// Copyright (C) 2019-2020 Dylech30th +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as +// published by the Free Software Foundation, either version 3 of the +// License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see . + +#endregion + +using System; +using System.Collections.Generic; +using Newtonsoft.Json; + +namespace Pixeval.Data.Web.Response +{ + public class TrendingTagResponse + { + [JsonProperty("trend_tags")] + public List TrendTags { get; set; } + + public class TrendTag + { + [JsonProperty("tag")] + public string TagStr { get; set; } + + [JsonProperty("translated_name")] + public string TranslatedName { get; set; } + + [JsonProperty("illust")] + public Illust Illust { get; set; } + } + + public class Illust + { + [JsonProperty("id")] + public long Id { get; set; } + + [JsonProperty("title")] + public string Title { get; set; } + + [JsonProperty("type")] + public string Type { get; set; } + + [JsonProperty("image_urls")] + public ImageUrls ImageUrls { get; set; } + + [JsonProperty("caption")] + public string Caption { get; set; } + + [JsonProperty("restrict")] + public long Restrict { get; set; } + + [JsonProperty("user")] + public User User { get; set; } + + [JsonProperty("tags")] + public List Tags { get; set; } + + [JsonProperty("tools")] + public List Tools { get; set; } + + [JsonProperty("create_date")] + public DateTimeOffset CreateDate { get; set; } + + [JsonProperty("page_count")] + public long PageCount { get; set; } + + [JsonProperty("width")] + public long Width { get; set; } + + [JsonProperty("height")] + public long Height { get; set; } + + [JsonProperty("sanity_level")] + public long SanityLevel { get; set; } + + [JsonProperty("x_restrict")] + public long XRestrict { get; set; } + + [JsonProperty("meta_single_page")] + public MetaSinglePage MetaSinglePage { get; set; } + + [JsonProperty("meta_pages")] + public List MetaPages { get; set; } + + [JsonProperty("total_view")] + public long TotalView { get; set; } + + [JsonProperty("total_bookmarks")] + public long TotalBookmarks { get; set; } + + [JsonProperty("is_bookmarked")] + public bool IsBookmarked { get; set; } + + [JsonProperty("visible")] + public bool Visible { get; set; } + + [JsonProperty("is_muted")] + public bool IsMuted { get; set; } + } + + public class ImageUrls + { + [JsonProperty("square_medium")] + public string SquareMedium { get; set; } + + [JsonProperty("medium")] + public string Medium { get; set; } + + [JsonProperty("large")] + public string Large { get; set; } + + [JsonProperty("original", NullValueHandling = NullValueHandling.Ignore)] + public string Original { get; set; } + } + + public class MetaPage + { + [JsonProperty("image_urls")] + public ImageUrls ImageUrls { get; set; } + } + + public class MetaSinglePage + { + [JsonProperty("original_image_url", NullValueHandling = NullValueHandling.Ignore)] + public string OriginalImageUrl { get; set; } + } + + public class Tag + { + [JsonProperty("name")] + public string Name { get; set; } + + [JsonProperty("translated_name")] + public string TranslatedName { get; set; } + } + + public class User + { + [JsonProperty("id")] + public long Id { get; set; } + + [JsonProperty("name")] + public string Name { get; set; } + + [JsonProperty("account")] + public string Account { get; set; } + + [JsonProperty("profile_image_urls")] + public ProfileImageUrls ProfileImageUrls { get; set; } + + [JsonProperty("is_followed")] + public bool IsFollowed { get; set; } + } + + public class ProfileImageUrls + { + [JsonProperty("medium")] + public string Medium { get; set; } + } + } +} \ No newline at end of file diff --git a/Pixeval/Data/Web/Response/UgoiraMetadataResponse.cs b/Pixeval/Data/Web/Response/UgoiraMetadataResponse.cs new file mode 100644 index 00000000..3730603e --- /dev/null +++ b/Pixeval/Data/Web/Response/UgoiraMetadataResponse.cs @@ -0,0 +1,55 @@ +#region Copyright (C) 2019-2020 Dylech30th. All rights reserved. + +// Pixeval - A Strong, Fast and Flexible Pixiv Client +// Copyright (C) 2019-2020 Dylech30th +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as +// published by the Free Software Foundation, either version 3 of the +// License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see . + +#endregion + +using System.Collections.Generic; +using Newtonsoft.Json; + +namespace Pixeval.Data.Web.Response +{ + public class UgoiraMetadataResponse + { + [JsonProperty("ugoira_metadata")] + public UgoiraMetadata UgoiraMetadataInfo { get; set; } + + public class UgoiraMetadata + { + [JsonProperty("zip_urls")] + public ZipUrls ZipUrls { get; set; } + + [JsonProperty("frames")] + public List Frames { get; set; } + } + + public class Frame + { + [JsonProperty("file")] + public string File { get; set; } + + [JsonProperty("delay")] + public long Delay { get; set; } + } + + public class ZipUrls + { + [JsonProperty("medium")] + public string Medium { get; set; } + } + } +} \ No newline at end of file diff --git a/Pixeval/Data/Web/Response/UploadResponse.cs b/Pixeval/Data/Web/Response/UploadResponse.cs new file mode 100644 index 00000000..dc36ebbd --- /dev/null +++ b/Pixeval/Data/Web/Response/UploadResponse.cs @@ -0,0 +1,209 @@ +#region Copyright (C) 2019-2020 Dylech30th. All rights reserved. + +// Pixeval - A Strong, Fast and Flexible Pixiv Client +// Copyright (C) 2019-2020 Dylech30th +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as +// published by the Free Software Foundation, either version 3 of the +// License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see . + +#endregion + +using System; +using System.Collections.Generic; +using System.Linq; +using Newtonsoft.Json; +using Pixeval.Data.ViewModel; +using Pixeval.Objects.Generic; + +namespace Pixeval.Data.Web.Response +{ + public class UploadResponse + { + [JsonProperty("illusts")] + public List Illusts { get; set; } + + [JsonProperty("next_url")] + public string NextUrl { get; set; } + + public class Illust : IParser + { + [JsonProperty("id")] + public long Id { get; set; } + + [JsonProperty("title")] + public string Title { get; set; } + + [JsonProperty("type")] + public string Type { get; set; } + + [JsonProperty("image_urls")] + public ImageUrls ImageUrls { get; set; } + + [JsonProperty("caption")] + public string Caption { get; set; } + + [JsonProperty("restrict")] + public long Restrict { get; set; } + + [JsonProperty("user")] + public User User { get; set; } + + [JsonProperty("tags")] + public List Tags { get; set; } + + [JsonProperty("tools")] + public List Tools { get; set; } + + [JsonProperty("create_date")] + public DateTimeOffset CreateDate { get; set; } + + [JsonProperty("page_count")] + public long PageCount { get; set; } + + [JsonProperty("width")] + public long Width { get; set; } + + [JsonProperty("height")] + public long Height { get; set; } + + [JsonProperty("sanity_level")] + public long SanityLevel { get; set; } + + [JsonProperty("x_restrict")] + public long XRestrict { get; set; } + + [JsonProperty("meta_single_page")] + public MetaSinglePage MetaSinglePage { get; set; } + + [JsonProperty("meta_pages")] + public List MetaPages { get; set; } + + [JsonProperty("total_view")] + public long TotalView { get; set; } + + [JsonProperty("total_bookmarks")] + public long TotalBookmarks { get; set; } + + [JsonProperty("is_bookmarked")] + public bool IsBookmarked { get; set; } + + [JsonProperty("visible")] + public bool Visible { get; set; } + + [JsonProperty("is_muted")] + public bool IsMuted { get; set; } + + [JsonProperty("total_comments")] + public long TotalComments { get; set; } + + public Illustration Parse() + { + return new Illustration + { + Bookmark = (int) TotalBookmarks, + Id = Id.ToString(), + IsLiked = IsBookmarked, + IsUgoira = Type == "ugoira", + IsManga = !MetaPages.IsNullOrEmpty(), + Origin = MetaSinglePage.OriginalImageUrl ?? ImageUrls.Large, + Large = ImageUrls.Large, + Tags = Tags.Select(t => new ViewModel.Tag { Name = t.Name, TranslatedName = t.TranslatedName }), + Thumbnail = ImageUrls.Medium ?? ImageUrls.SquareMedium, + Title = Title, + UserId = User.Id.ToString(), + UserName = User.Name, + Resolution = $"{Width}x{Height}", + ViewCount = (int) TotalView, + PublishDate = CreateDate + }.Apply(i => + { + if (i.IsManga) + { + i.MangaMetadata = MetaPages.Select(p => + { + var page = (Illustration) i.Clone(); + page.Thumbnail = p.ImageUrls.Medium ?? p.ImageUrls.SquareMedium; + page.Origin = p.ImageUrls.Original; + page.Large = p.ImageUrls.Large; + return page; + }).ToArray(); + foreach (var illustration in i.MangaMetadata) + { + illustration.MangaMetadata = i.MangaMetadata; + } + } + }); + } + } + + public class ImageUrls + { + [JsonProperty("square_medium")] + public string SquareMedium { get; set; } + + [JsonProperty("medium")] + public string Medium { get; set; } + + [JsonProperty("large")] + public string Large { get; set; } + + [JsonProperty("original", NullValueHandling = NullValueHandling.Ignore)] + public string Original { get; set; } + } + + public class MetaPage + { + [JsonProperty("image_urls")] + public ImageUrls ImageUrls { get; set; } + } + + public class MetaSinglePage + { + [JsonProperty("original_image_url", NullValueHandling = NullValueHandling.Ignore)] + public string OriginalImageUrl { get; set; } + } + + public class Tag + { + [JsonProperty("name")] + public string Name { get; set; } + + [JsonProperty("translated_name")] + public string TranslatedName { get; set; } + } + + public class User + { + [JsonProperty("id")] + public long Id { get; set; } + + [JsonProperty("name")] + public string Name { get; set; } + + [JsonProperty("account")] + public string Account { get; set; } + + [JsonProperty("profile_image_urls")] + public ProfileImageUrls ProfileImageUrls { get; set; } + + [JsonProperty("is_followed")] + public bool IsFollowed { get; set; } + } + + public class ProfileImageUrls + { + [JsonProperty("medium")] + public string Medium { get; set; } + } + } +} \ No newline at end of file diff --git a/Pixeval/Data/Web/Response/UserInformationResponse.cs b/Pixeval/Data/Web/Response/UserInformationResponse.cs new file mode 100644 index 00000000..cd7e1b95 --- /dev/null +++ b/Pixeval/Data/Web/Response/UserInformationResponse.cs @@ -0,0 +1,198 @@ +#region Copyright (C) 2019-2020 Dylech30th. All rights reserved. + +// Pixeval - A Strong, Fast and Flexible Pixiv Client +// Copyright (C) 2019-2020 Dylech30th +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as +// published by the Free Software Foundation, either version 3 of the +// License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see . + +#endregion + +using Newtonsoft.Json; + +namespace Pixeval.Data.Web.Response +{ + public class UserInformationResponse + { + [JsonProperty("user")] + public User UserEntity { get; set; } + + [JsonProperty("profile")] + public Profile UserProfile { get; set; } + + [JsonProperty("profile_publicity")] + public ProfilePublicity UserProfilePublicity { get; set; } + + [JsonProperty("workspace")] + public Workspace UserWorkspace { get; set; } + + public class Profile + { + [JsonProperty("webpage")] + public string Webpage { get; set; } + + [JsonProperty("gender")] + public string Gender { get; set; } + + [JsonProperty("birth")] + public string Birth { get; set; } + + [JsonProperty("birth_day")] + public string BirthDay { get; set; } + + [JsonProperty("birth_year")] + public long BirthYear { get; set; } + + [JsonProperty("region")] + public string Region { get; set; } + + [JsonProperty("address_id")] + public long AddressId { get; set; } + + [JsonProperty("country_code")] + public string CountryCode { get; set; } + + [JsonProperty("job")] + public string Job { get; set; } + + [JsonProperty("job_id")] + public long JobId { get; set; } + + [JsonProperty("total_follow_users")] + public long TotalFollowUsers { get; set; } + + [JsonProperty("total_mypixiv_users")] + public long TotalMypixivUsers { get; set; } + + [JsonProperty("total_illusts")] + public long TotalIllusts { get; set; } + + [JsonProperty("total_manga")] + public long TotalManga { get; set; } + + [JsonProperty("total_novels")] + public long TotalNovels { get; set; } + + [JsonProperty("total_illust_bookmarks_public")] + public long TotalIllustBookmarksPublic { get; set; } + + [JsonProperty("total_illust_series")] + public long TotalIllustSeries { get; set; } + + [JsonProperty("total_novel_series")] + public long TotalNovelSeries { get; set; } + + [JsonProperty("background_image_url")] + public string BackgroundImageUrl { get; set; } + + [JsonProperty("twitter_account")] + public string TwitterAccount { get; set; } + + [JsonProperty("twitter_url")] + public string TwitterUrl { get; set; } + + [JsonProperty("is_premium")] + public bool IsPremium { get; set; } + + [JsonProperty("is_using_custom_profile_image")] + public bool IsUsingCustomProfileImage { get; set; } + } + + public class ProfilePublicity + { + [JsonProperty("gender")] + public string Gender { get; set; } + + [JsonProperty("region")] + public string Region { get; set; } + + [JsonProperty("birth_day")] + public string BirthDay { get; set; } + + [JsonProperty("birth_year")] + public string BirthYear { get; set; } + + [JsonProperty("job")] + public string Job { get; set; } + + [JsonProperty("pawoo")] + public bool Pawoo { get; set; } + } + + public class User + { + [JsonProperty("id")] + public long Id { get; set; } + + [JsonProperty("name")] + public string Name { get; set; } + + [JsonProperty("account")] + public string Account { get; set; } + + [JsonProperty("profile_image_urls")] + public ProfileImageUrls ProfileImageUrls { get; set; } + + [JsonProperty("comment")] + public string Comment { get; set; } + + [JsonProperty("is_followed")] + public bool IsFollowed { get; set; } + } + + public class ProfileImageUrls + { + [JsonProperty("medium")] + public string Medium { get; set; } + } + + public class Workspace + { + [JsonProperty("pc")] + public string Pc { get; set; } + + [JsonProperty("monitor")] + public string Monitor { get; set; } + + [JsonProperty("tool")] + public string Tool { get; set; } + + [JsonProperty("scanner")] + public string Scanner { get; set; } + + [JsonProperty("tablet")] + public string Tablet { get; set; } + + [JsonProperty("mouse")] + public string Mouse { get; set; } + + [JsonProperty("printer")] + public string Printer { get; set; } + + [JsonProperty("desktop")] + public string Desktop { get; set; } + + [JsonProperty("music")] + public string Music { get; set; } + + [JsonProperty("desk")] + public string Desk { get; set; } + + [JsonProperty("chair")] + public string Chair { get; set; } + + [JsonProperty("comment")] + public string Comment { get; set; } + } + } +} \ No newline at end of file diff --git a/Pixeval/Data/Web/Response/UserNavResponse.cs b/Pixeval/Data/Web/Response/UserNavResponse.cs new file mode 100644 index 00000000..7ccb440d --- /dev/null +++ b/Pixeval/Data/Web/Response/UserNavResponse.cs @@ -0,0 +1,106 @@ +#region Copyright (C) 2019-2020 Dylech30th. All rights reserved. + +// Pixeval - A Strong, Fast and Flexible Pixiv Client +// Copyright (C) 2019-2020 Dylech30th +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as +// published by the Free Software Foundation, either version 3 of the +// License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see . + +#endregion + +using System.Collections.Generic; +using Newtonsoft.Json; +using Pixeval.Objects.Primitive; + +namespace Pixeval.Data.Web.Response +{ + public class UserNavResponse + { + [JsonProperty("user_previews")] + public List UserPreviews { get; set; } + + [JsonProperty("next_url")] + public string NextUrl { get; set; } + + public override string ToString() + { + return this.ToJson(); + } + + public class UserPreview + { + [JsonProperty("user")] + public User User { get; set; } + + [JsonProperty("illusts")] + public List Illusts { get; set; } + + [JsonProperty("is_muted")] + public bool IsMuted { get; set; } + } + + public class Illust + { + [JsonProperty("type")] + public string Type { get; set; } + + [JsonProperty("image_urls")] + public ImageUrls ImageUrl { get; set; } + + [JsonProperty("tools")] + public string[] Tools { get; set; } + + [JsonProperty("page_count")] + public long PageCount { get; set; } + } + + public class ImageUrls + { + [JsonProperty("square_medium")] + public string SquareMedium { get; set; } + + [JsonProperty("medium")] + public string Medium { get; set; } + + [JsonProperty("large")] + public string Large { get; set; } + + [JsonProperty("original", NullValueHandling = NullValueHandling.Ignore)] + public string Original { get; set; } + } + + public class User + { + [JsonProperty("id")] + public long Id { get; set; } + + [JsonProperty("name")] + public string Name { get; set; } + + [JsonProperty("account")] + public string Account { get; set; } + + [JsonProperty("profile_image_urls")] + public ProfileImageUrls ProfileImageUrls { get; set; } + + [JsonProperty("is_followed")] + public bool IsFollowed { get; set; } + } + + public class ProfileImageUrls + { + [JsonProperty("medium")] + public string Medium { get; set; } + } + } +} \ No newline at end of file diff --git a/Pixeval/Data/Web/Response/UserUpdateResponse.cs b/Pixeval/Data/Web/Response/UserUpdateResponse.cs new file mode 100644 index 00000000..e3ea2a27 --- /dev/null +++ b/Pixeval/Data/Web/Response/UserUpdateResponse.cs @@ -0,0 +1,207 @@ +#region Copyright (C) 2019-2020 Dylech30th. All rights reserved. + +// Pixeval - A Strong, Fast and Flexible Pixiv Client +// Copyright (C) 2019-2020 Dylech30th +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as +// published by the Free Software Foundation, either version 3 of the +// License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see . + +#endregion + +using System; +using System.Collections.Generic; +using System.Linq; +using Newtonsoft.Json; +using Pixeval.Data.ViewModel; +using Pixeval.Objects.Generic; +using Pixeval.Objects.Primitive; + +namespace Pixeval.Data.Web.Response +{ + public class UserUpdateResponse + { + [JsonProperty("illusts")] + public List Illusts { get; set; } + + [JsonProperty("next_url")] + public string NextUrl { get; set; } + + public class Illust : IParser + { + [JsonProperty("id")] + public long Id { get; set; } + + [JsonProperty("title")] + public string Title { get; set; } + + [JsonProperty("type")] + public string Type { get; set; } + + [JsonProperty("image_urls")] + public ImageUrls ImageUrls { get; set; } + + [JsonProperty("caption")] + public string Caption { get; set; } + + [JsonProperty("restrict")] + public long Restrict { get; set; } + + [JsonProperty("user")] + public User User { get; set; } + + [JsonProperty("tags")] + public List Tags { get; set; } + + [JsonProperty("tools")] + public List Tools { get; set; } + + [JsonProperty("create_date")] + public DateTimeOffset CreateDate { get; set; } + + [JsonProperty("page_count")] + public long PageCount { get; set; } + + [JsonProperty("width")] + public long Width { get; set; } + + [JsonProperty("height")] + public long Height { get; set; } + + [JsonProperty("sanity_level")] + public long SanityLevel { get; set; } + + [JsonProperty("x_restrict")] + public long XRestrict { get; set; } + + [JsonProperty("meta_single_page")] + public MetaSinglePage MetaSinglePage { get; set; } + + [JsonProperty("meta_pages")] + public List MetaPages { get; set; } + + [JsonProperty("total_view")] + public long TotalView { get; set; } + + [JsonProperty("total_bookmarks")] + public long TotalBookmarks { get; set; } + + [JsonProperty("is_bookmarked")] + public bool IsBookmarked { get; set; } + + [JsonProperty("visible")] + public bool Visible { get; set; } + + [JsonProperty("is_muted")] + public bool IsMuted { get; set; } + + public Illustration Parse() + { + return new Illustration + { + Bookmark = (int) TotalBookmarks, + Id = Id.ToString(), + IsLiked = IsBookmarked, + IsManga = PageCount != 1, + IsUgoira = Type == "ugoira", + Origin = MetaSinglePage.OriginalImageUrl.IsNullOrEmpty() ? ImageUrls.Large : MetaSinglePage.OriginalImageUrl, + Large = ImageUrls.Large, + Tags = Tags.Select(t => new ViewModel.Tag { Name = t.Name, TranslatedName = t.TranslatedName }), + Thumbnail = ImageUrls.Medium, + UserId = User.Id.ToString(), + UserName = User.Name, + Title = Title, + Resolution = $"{Width}x{Height}", + ViewCount = (int) TotalView, + PublishDate = CreateDate + }.Apply(i => + { + if (i.IsManga) + { + i.MangaMetadata = MetaPages.Select(p => + { + var page = (Illustration) i.Clone(); + page.Thumbnail = p.ImageUrls.Medium; + page.Origin = p.ImageUrls.Original; + page.Large = p.ImageUrls.Large; + return page; + }).ToArray(); + foreach (var illustration in i.MangaMetadata) + { + illustration.MangaMetadata = i.MangaMetadata; + } + } + }); + } + } + + public class ImageUrls + { + [JsonProperty("square_medium")] + public string SquareMedium { get; set; } + + [JsonProperty("medium")] + public string Medium { get; set; } + + [JsonProperty("large")] + public string Large { get; set; } + + [JsonProperty("original", NullValueHandling = NullValueHandling.Ignore)] + public string Original { get; set; } + } + + public class MetaPage + { + [JsonProperty("image_urls")] + public ImageUrls ImageUrls { get; set; } + } + + public class MetaSinglePage + { + [JsonProperty("original_image_url", NullValueHandling = NullValueHandling.Ignore)] + public string OriginalImageUrl { get; set; } + } + + public class Tag + { + [JsonProperty("name")] + public string Name { get; set; } + + [JsonProperty("translated_name")] + public string TranslatedName { get; set; } + } + + public class User + { + [JsonProperty("id")] + public long Id { get; set; } + + [JsonProperty("name")] + public string Name { get; set; } + + [JsonProperty("account")] + public string Account { get; set; } + + [JsonProperty("profile_image_urls")] + public ProfileImageUrls ProfileImageUrls { get; set; } + + [JsonProperty("is_followed")] + public bool IsFollowed { get; set; } + } + + public class ProfileImageUrls + { + [JsonProperty("medium")] + public string Medium { get; set; } + } + } +} \ No newline at end of file diff --git a/Pixeval/Data/Web/Response/WebApiUserDetailResponse.cs b/Pixeval/Data/Web/Response/WebApiUserDetailResponse.cs new file mode 100644 index 00000000..b7c771d4 --- /dev/null +++ b/Pixeval/Data/Web/Response/WebApiUserDetailResponse.cs @@ -0,0 +1,54 @@ +#region Copyright (C) 2019-2020 Dylech30th. All rights reserved. + +// Pixeval - A Strong, Fast and Flexible Pixiv Client +// Copyright (C) 2019-2020 Dylech30th +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as +// published by the Free Software Foundation, either version 3 of the +// License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see . + +#endregion + +using Newtonsoft.Json; + +namespace Pixeval.Data.Web.Response +{ + public class WebApiUserDetailResponse + { + [JsonProperty("body")] + public Body ResponseBody { get; set; } + + public class Body + { + [JsonProperty("user_details")] + public UserDetails UserDetails { get; set; } + } + + public class UserDetails + { + [JsonProperty("cover_image")] + public CoverImage CoverImage { get; set; } + } + + public class CoverImage + { + [JsonProperty("profile_cover_image")] + public ProfileCoverImage ProfileCoverImage { get; set; } + } + + public class ProfileCoverImage + { + [JsonProperty("720x360")] + public string The720X360 { get; set; } + } + } +} \ No newline at end of file diff --git a/Pixeval/FodyWeavers.xml b/Pixeval/FodyWeavers.xml new file mode 100644 index 00000000..d5abfed8 --- /dev/null +++ b/Pixeval/FodyWeavers.xml @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/Pixeval/FodyWeavers.xsd b/Pixeval/FodyWeavers.xsd new file mode 100644 index 00000000..221aeb8a --- /dev/null +++ b/Pixeval/FodyWeavers.xsd @@ -0,0 +1,64 @@ + + + + + + + + + + + Used to control if the On_PropertyName_Changed feature is enabled. + + + + + Used to change the name of the method that fires the notify event. This is a string that accepts multiple values in a comma separated form. + + + + + Used to control if equality checks should be inserted. If false, equality checking will be disabled for the project. + + + + + Used to control if equality checks should use the Equals method resolved from the base class. + + + + + Used to control if equality checks should use the static Equals method resolved from the base class. + + + + + Used to turn off build warnings from this weaver. + + + + + Used to turn off build warnings about mismatched On_PropertyName_Changed methods. + + + + + + + + 'true' to run assembly verification (PEVerify) on the target assembly after all weavers have been executed. + + + + + A comma-separated list of error codes that can be safely ignored in assembly verification. + + + + + 'false' to turn off automatic generation of the XML Schema file. + + + + + \ No newline at end of file diff --git a/Pixeval/Objects/Caching/CachingPolicy.cs b/Pixeval/Objects/Caching/CachingPolicy.cs new file mode 100644 index 00000000..61a724ad --- /dev/null +++ b/Pixeval/Objects/Caching/CachingPolicy.cs @@ -0,0 +1,27 @@ +#region Copyright (C) 2019-2020 Dylech30th. All rights reserved. + +// Pixeval - A Strong, Fast and Flexible Pixiv Client +// Copyright (C) 2019-2020 Dylech30th +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as +// published by the Free Software Foundation, either version 3 of the +// License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see . + +#endregion + +namespace Pixeval.Objects.Caching +{ + public enum CachingPolicy + { + Memory, File + } +} \ No newline at end of file diff --git a/Pixeval/Objects/Caching/FileCache.cs b/Pixeval/Objects/Caching/FileCache.cs new file mode 100644 index 00000000..adcfdfa7 --- /dev/null +++ b/Pixeval/Objects/Caching/FileCache.cs @@ -0,0 +1,117 @@ +#region Copyright (C) 2019-2020 Dylech30th. All rights reserved. + +// Pixeval - A Strong, Fast and Flexible Pixiv Client +// Copyright (C) 2019-2020 Dylech30th +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as +// published by the Free Software Foundation, either version 3 of the +// License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see . + +#endregion + +using System; +using System.Collections; +using System.Collections.Concurrent; +using System.Collections.Generic; +using System.IO; +using System.Threading; +using System.Threading.Tasks; + +namespace Pixeval.Objects.Caching +{ + public class FileCache : IWeakCacheProvider, IEnumerable> where T : class + { + private readonly Func cachingPolicy; + private readonly ConcurrentDictionary fileMapping = new ConcurrentDictionary(); + private readonly string initDirectory; + private readonly Func restorePolicy; + + public FileCache(string initDirectory, Func cachingPolicy, Func restorePolicy) + { + this.cachingPolicy = cachingPolicy; + this.restorePolicy = restorePolicy; + this.initDirectory = initDirectory; + + Directory.CreateDirectory(initDirectory); + } + + public IEnumerator> GetEnumerator() + { + return fileMapping.GetEnumerator(); + } + + IEnumerator IEnumerable.GetEnumerator() + { + return GetEnumerator(); + } + + public void Attach(ref T key, THash associateWith) + { + if (associateWith == null || key == null) + { + return; + } + var path = Path.Combine(initDirectory, IWeakCacheProvider.HashKey(associateWith) + ".tmp"); + if (!File.Exists(path)) + { + var s = cachingPolicy(key); + key = null; + Task.Run(() => + { + fileMapping.TryAdd(associateWith, path); + WriteFile(path, s); + }); + } + } + + public void Detach(THash associateWith) + { + using var sem = new SemaphoreSlim(1); + var path = Path.Combine(initDirectory, IWeakCacheProvider.HashKey(associateWith) + "."); + if (File.Exists(path)) + { + fileMapping.TryRemove(associateWith, out _); + File.Delete(path); + } + } + + public async Task<(bool, T)> TryGet([NotNull] THash key) + { + if (fileMapping.TryGetValue(key, out var file) && File.Exists(file)) + { + await using var fileStream = File.OpenRead(file); + fileStream.Position = 0L; + await using Stream memoStream = new MemoryStream(); + await fileStream.CopyToAsync(memoStream); + return (true, restorePolicy(memoStream)); + } + + return (false, null); + } + + public void Clear() + { + using var sem = new SemaphoreSlim(1); + foreach (var file in Directory.GetFiles(initDirectory)) + { + File.Delete(file); + } + } + + private static async void WriteFile(string path, Stream src) + { + await using var fileStream = new FileStream(path, FileMode.OpenOrCreate, FileAccess.ReadWrite, FileShare.None); + src.Position = 0L; + await src.CopyToAsync(fileStream); + } + } +} \ No newline at end of file diff --git a/Pixeval/Objects/Caching/IWeakCacheProvider.cs b/Pixeval/Objects/Caching/IWeakCacheProvider.cs new file mode 100644 index 00000000..7b6ac036 --- /dev/null +++ b/Pixeval/Objects/Caching/IWeakCacheProvider.cs @@ -0,0 +1,40 @@ +#region Copyright (C) 2019-2020 Dylech30th. All rights reserved. + +// Pixeval - A Strong, Fast and Flexible Pixiv Client +// Copyright (C) 2019-2020 Dylech30th +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as +// published by the Free Software Foundation, either version 3 of the +// License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see . + +#endregion + +using System.Threading.Tasks; + +namespace Pixeval.Objects.Caching +{ + public interface IWeakCacheProvider where T : class + { + void Attach(ref T key, THash associateWith); + + void Detach(THash associateWith); + + Task<(bool, T)> TryGet(THash key); + + void Clear(); + + protected static int HashKey(THash key) + { + return key == null ? 0 : key.GetHashCode(); + } + } +} \ No newline at end of file diff --git a/Pixeval/Objects/Caching/MemoryCache.cs b/Pixeval/Objects/Caching/MemoryCache.cs new file mode 100644 index 00000000..a780545e --- /dev/null +++ b/Pixeval/Objects/Caching/MemoryCache.cs @@ -0,0 +1,73 @@ +#region Copyright (C) 2019-2020 Dylech30th. All rights reserved. + +// Pixeval - A Strong, Fast and Flexible Pixiv Client +// Copyright (C) 2019-2020 Dylech30th +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as +// published by the Free Software Foundation, either version 3 of the +// License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see . + +#endregion + +using System.Collections; +using System.Collections.Concurrent; +using System.Collections.Generic; +using System.Threading.Tasks; + +namespace Pixeval.Objects.Caching +{ + public class MemoryCache : IWeakCacheProvider, IEnumerable>> where T : class + { + public static readonly MemoryCache Shared = new MemoryCache(); + + private readonly ConcurrentDictionary> cache = new ConcurrentDictionary>(); + + public IEnumerator>> GetEnumerator() + { + return cache.GetEnumerator(); + } + + IEnumerator IEnumerable.GetEnumerator() + { + return GetEnumerator(); + } + + public void Attach(ref T key, THash associateWith) + { + if (associateWith == null || key == null) + { + return; + } + var weakRef = new WeakEntry(key); + key = null; + cache.TryAdd(IWeakCacheProvider.HashKey(associateWith), weakRef); + } + + public void Detach(THash associateWith) + { + cache.TryRemove(IWeakCacheProvider.HashKey(associateWith), out _); + } + + public Task<(bool, T)> TryGet([NotNull] THash key) + { + return cache.TryGetValue(IWeakCacheProvider.HashKey(key), out var weakRef) && weakRef.Target is { } target ? Task.FromResult((true, target)) : Task.FromResult((false, (T) null)); + } + + public void Clear() + { + lock (cache) + { + cache.Clear(); + } + } + } +} \ No newline at end of file diff --git a/Pixeval/Objects/Caching/WeakEntry.cs b/Pixeval/Objects/Caching/WeakEntry.cs new file mode 100644 index 00000000..cfc1ce98 --- /dev/null +++ b/Pixeval/Objects/Caching/WeakEntry.cs @@ -0,0 +1,74 @@ +#region Copyright (C) 2019-2020 Dylech30th. All rights reserved. + +// Pixeval - A Strong, Fast and Flexible Pixiv Client +// Copyright (C) 2019-2020 Dylech30th +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as +// published by the Free Software Foundation, either version 3 of the +// License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see . + +#endregion + +using System; +using System.Runtime.InteropServices; + +#nullable enable + +namespace Pixeval.Objects.Caching +{ + public class WeakEntry : IEquatable>, IDisposable where T : class + { + private readonly int hashCode; + + private GCHandle gcHandle; + + public WeakEntry(T target) + { + hashCode = target.GetHashCode(); + gcHandle = GCHandle.Alloc(target, GCHandleType.Weak); + } + + public bool IsAlive => gcHandle.Target != null; + + public T? Target => gcHandle.Target as T; + + public void Dispose() + { + gcHandle.Free(); + GC.SuppressFinalize(this); + } + + public bool Equals(WeakEntry? other) + { + return other != null && ReferenceEquals(other.Target, Target); + } + + ~WeakEntry() + { + Dispose(); + } + + public override bool Equals(object? obj) + { + if (obj is WeakEntry weakEntry) + { + return Equals(weakEntry); + } + return false; + } + + public override int GetHashCode() + { + return hashCode; + } + } +} \ No newline at end of file diff --git a/Pixeval/Objects/DialogResult.cs b/Pixeval/Objects/DialogResult.cs new file mode 100644 index 00000000..fc7fa3fc --- /dev/null +++ b/Pixeval/Objects/DialogResult.cs @@ -0,0 +1,27 @@ +#region Copyright (C) 2019-2020 Dylech30th. All rights reserved. + +// Pixeval - A Strong, Fast and Flexible Pixiv Client +// Copyright (C) 2019-2020 Dylech30th +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as +// published by the Free Software Foundation, either version 3 of the +// License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see . + +#endregion + +namespace Pixeval.Objects +{ + public enum DialogResult + { + Yes, No + } +} \ No newline at end of file diff --git a/Pixeval/Objects/Exceptions/AttributeNotFoundException.cs b/Pixeval/Objects/Exceptions/AttributeNotFoundException.cs new file mode 100644 index 00000000..49c434cb --- /dev/null +++ b/Pixeval/Objects/Exceptions/AttributeNotFoundException.cs @@ -0,0 +1,44 @@ +#region Copyright (C) 2019-2020 Dylech30th. All rights reserved. + +// Pixeval - A Strong, Fast and Flexible Pixiv Client +// Copyright (C) 2019-2020 Dylech30th +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as +// published by the Free Software Foundation, either version 3 of the +// License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see . + +#endregion + +using System; +using System.Runtime.Serialization; + +namespace Pixeval.Objects.Exceptions +{ + public class AttributeNotFoundException : Exception + { + public AttributeNotFoundException() + { + } + + protected AttributeNotFoundException(SerializationInfo info, StreamingContext context) : base(info, context) + { + } + + public AttributeNotFoundException(string message) : base(message) + { + } + + public AttributeNotFoundException(string message, Exception innerException) : base(message, innerException) + { + } + } +} \ No newline at end of file diff --git a/Pixeval/Objects/Exceptions/AuthenticateFailedException.cs b/Pixeval/Objects/Exceptions/AuthenticateFailedException.cs new file mode 100644 index 00000000..3f25448b --- /dev/null +++ b/Pixeval/Objects/Exceptions/AuthenticateFailedException.cs @@ -0,0 +1,44 @@ +#region Copyright (C) 2019-2020 Dylech30th. All rights reserved. + +// Pixeval - A Strong, Fast and Flexible Pixiv Client +// Copyright (C) 2019-2020 Dylech30th +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as +// published by the Free Software Foundation, either version 3 of the +// License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see . + +#endregion + +using System; +using System.Runtime.Serialization; + +namespace Pixeval.Objects.Exceptions +{ + public class AuthenticateFailedException : Exception + { + public AuthenticateFailedException() + { + } + + protected AuthenticateFailedException(SerializationInfo info, StreamingContext context) : base(info, context) + { + } + + public AuthenticateFailedException(string message) : base(message) + { + } + + public AuthenticateFailedException(string message, Exception innerException) : base(message, innerException) + { + } + } +} \ No newline at end of file diff --git a/Pixeval/Objects/Exceptions/Logger/ExceptionDumper.cs b/Pixeval/Objects/Exceptions/Logger/ExceptionDumper.cs new file mode 100644 index 00000000..f1943de3 --- /dev/null +++ b/Pixeval/Objects/Exceptions/Logger/ExceptionDumper.cs @@ -0,0 +1,137 @@ +#region Copyright (C) 2019-2020 Dylech30th. All rights reserved. + +// Pixeval - A Strong, Fast and Flexible Pixiv Client +// Copyright (C) 2019-2020 Dylech30th +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as +// published by the Free Software Foundation, either version 3 of the +// License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see . + +#endregion + +using System; +using System.Globalization; +using System.IO; +using System.Linq; +using System.Runtime.InteropServices; +using System.Text; +using System.Threading; +using Microsoft.Win32; +using Pixeval.Objects.Primitive; +using Refit; + +namespace Pixeval.Objects.Exceptions.Logger +{ + [StructLayout(LayoutKind.Sequential)] + public struct MemoryStatusEx + { + public uint dwLength; + public uint dwMemoryLoad; + public ulong ullTotalPhys; + public ulong ullAvailPhys; + public ulong ullTotalPageFile; + public ulong ullAvailPageFile; + public ulong ullTotalVirtual; + public ulong ullAvailVirtual; + public ulong ullAvailExtendedVirtual; + } + + + public class ExceptionDumper + { + public static async void WriteException(Exception e) + { + using var semaphore = new SemaphoreSlim(1); + await semaphore.WaitAsync(TimeSpan.FromSeconds(5)); + + var exceptionMessage = e is ApiException exception ? exception.Content + Environment.NewLine + exception : e.ToString(); + var sb = new StringBuilder(); + sb.AppendLine(@"Pixeval - A Strong, Fast and Flexible Pixiv Client"); + sb.AppendLine(@"Copyright (C) 2019-2020 Dylech30th"); + sb.AppendLine(); + sb.AppendLine(@"We have encountered a problem. A dump file with error snapshot has been created."); + sb.AppendLine(@"In order to help with diagnosis and debug, Pixeval will collect some information contains: "); + sb.AppendLine(@" ¡¤ Computer Architecture"); + sb.AppendLine(@" ¡¤ Operating System"); + sb.AppendLine(@" ¡¤ Event Log"); + sb.AppendLine(@" ¡¤ Exception Message"); + sb.AppendLine(); + sb.AppendLine(@"Begin Dump Information"); + sb.AppendLine($" Pixeval Version: {AppContext.CurrentVersion}"); + sb.AppendLine($" Creation: {DateTime.Now}"); + sb.AppendLine(@"End Dump Information"); + sb.AppendLine(); + sb.AppendLine(@"Begin Debugging Information Collection"); + sb.AppendLine(@" Begin Computer Architecture"); + sb.AppendLine($" CPU Architecture: {Environment.GetEnvironmentVariable("PROCESSOR_ARCHITECTURE")}"); + sb.AppendLine($" Is 64-bit Platform: {Environment.Is64BitOperatingSystem}"); + sb.AppendLine($" Is 64-bit Process: {Environment.Is64BitProcess}"); + sb.AppendLine($" Total Installed RAM: {GetTotalInstalledMemory()} GB"); + sb.AppendLine($" Total Available RAM: {GetAvailableMemory()} MB"); + sb.AppendLine(@" End Computer Architecture"); + sb.AppendLine(); + sb.AppendLine(@" Begin Operating System"); + sb.AppendLine($" OS Version String: {Environment.OSVersion.VersionString}"); + sb.AppendLine($" OS Version {Environment.OSVersion.Version}"); + sb.AppendLine($" Service Pack Version: {(Environment.OSVersion.ServicePack.IsNullOrEmpty() ? "Not Installed" : "Environment.OSVersion.ServicePack")}"); + sb.AppendLine($" Visual C++ Redistributable Version: {GetCppRedistributableVersion()}"); + sb.AppendLine(@" End Operating System"); + sb.AppendLine(); + sb.AppendLine(@" Begin Exception Log"); + sb.AppendLine(@" Exception: "); + sb.AppendLine(FormatMultilineData(exceptionMessage, 3)); + sb.AppendLine(" End Exception Log"); + sb.AppendLine(@"End Debugging Information Collection"); + try + { + await File.WriteAllTextAsync(Path.Combine(AppContext.ExceptionReportFolder, $"{DateTime.Now.ToString(CultureInfo.InvariantCulture)}.txt".Replace("/", "-").Replace(":", "-")), sb.ToString()); + } + catch + { + // ignore + } + } + + private static string FormatMultilineData(string data, int indent) + { + var indentation = new string(' ', indent * 4); + return data.Split('\n').Select(s => indentation + s).Join('\n'); + } + + private static string GetCppRedistributableVersion() + { + using var key = Registry.LocalMachine.OpenSubKey(@"SOFTWARE\Wow6432Node\Microsoft\VisualStudio\14.0\VC\Runtimes\x64"); + return key == null ? "Not Installed" : key.GetValue("Bld").ToString(); + } + + private static ulong GetTotalInstalledMemory() + { + return GetMemoryStatusExInternal().ullTotalPhys / 1024 / 1024 / 1024; + } + + private static ulong GetAvailableMemory() + { + return GetMemoryStatusExInternal().ullAvailPhys / 1024 / 1024; + } + + private static MemoryStatusEx GetMemoryStatusExInternal() + { + var memInfo = new MemoryStatusEx { dwLength = 64 }; + //此方法为手动Hack,按照填充规则计算大小,祝我好运 + _ = GlobalMemoryStatusEx(ref memInfo); //实践证明,必须有人接收返回值,否则会报错 + return memInfo; + } + + [DllImport("kernel32.dll", EntryPoint = "GlobalMemoryStatusEx", CallingConvention = CallingConvention.StdCall)] //此处一定要用Ex,否则内存计算不全 + private static extern int GlobalMemoryStatusEx(ref MemoryStatusEx lpBuffer); + } +} \ No newline at end of file diff --git a/Pixeval/Objects/Exceptions/QueryNotRespondingException.cs b/Pixeval/Objects/Exceptions/QueryNotRespondingException.cs new file mode 100644 index 00000000..f97e0840 --- /dev/null +++ b/Pixeval/Objects/Exceptions/QueryNotRespondingException.cs @@ -0,0 +1,44 @@ +#region Copyright (C) 2019-2020 Dylech30th. All rights reserved. + +// Pixeval - A Strong, Fast and Flexible Pixiv Client +// Copyright (C) 2019-2020 Dylech30th +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as +// published by the Free Software Foundation, either version 3 of the +// License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see . + +#endregion + +using System; +using System.Runtime.Serialization; + +namespace Pixeval.Objects.Exceptions +{ + public class QueryNotRespondingException : Exception + { + public QueryNotRespondingException() + { + } + + protected QueryNotRespondingException(SerializationInfo info, StreamingContext context) : base(info, context) + { + } + + public QueryNotRespondingException(string message) : base(message) + { + } + + public QueryNotRespondingException(string message, Exception innerException) : base(message, innerException) + { + } + } +} \ No newline at end of file diff --git a/Pixeval/Objects/Exceptions/TokenNotFoundException.cs b/Pixeval/Objects/Exceptions/TokenNotFoundException.cs new file mode 100644 index 00000000..2db98d8d --- /dev/null +++ b/Pixeval/Objects/Exceptions/TokenNotFoundException.cs @@ -0,0 +1,44 @@ +#region Copyright (C) 2019-2020 Dylech30th. All rights reserved. + +// Pixeval - A Strong, Fast and Flexible Pixiv Client +// Copyright (C) 2019-2020 Dylech30th +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as +// published by the Free Software Foundation, either version 3 of the +// License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see . + +#endregion + +using System; +using System.Runtime.Serialization; + +namespace Pixeval.Objects.Exceptions +{ + public class TokenNotFoundException : Exception + { + public TokenNotFoundException() + { + } + + protected TokenNotFoundException(SerializationInfo info, StreamingContext context) : base(info, context) + { + } + + public TokenNotFoundException(string message) : base(message) + { + } + + public TokenNotFoundException(string message, Exception innerException) : base(message, innerException) + { + } + } +} \ No newline at end of file diff --git a/Pixeval/Objects/Exceptions/TypeMismatchException.cs b/Pixeval/Objects/Exceptions/TypeMismatchException.cs new file mode 100644 index 00000000..cb2e7741 --- /dev/null +++ b/Pixeval/Objects/Exceptions/TypeMismatchException.cs @@ -0,0 +1,44 @@ +#region Copyright (C) 2019-2020 Dylech30th. All rights reserved. + +// Pixeval - A Strong, Fast and Flexible Pixiv Client +// Copyright (C) 2019-2020 Dylech30th +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as +// published by the Free Software Foundation, either version 3 of the +// License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see . + +#endregion + +using System; +using System.Runtime.Serialization; + +namespace Pixeval.Objects.Exceptions +{ + public class TypeMismatchException : Exception + { + public TypeMismatchException() + { + } + + protected TypeMismatchException(SerializationInfo info, StreamingContext context) : base(info, context) + { + } + + public TypeMismatchException(string message) : base(message) + { + } + + public TypeMismatchException(string message, Exception innerException) : base(message, innerException) + { + } + } +} \ No newline at end of file diff --git a/Pixeval/Objects/Generic/Enumerates.cs b/Pixeval/Objects/Generic/Enumerates.cs new file mode 100644 index 00000000..3552ae44 --- /dev/null +++ b/Pixeval/Objects/Generic/Enumerates.cs @@ -0,0 +1,87 @@ +#region Copyright (C) 2019-2020 Dylech30th. All rights reserved. + +// Pixeval - A Strong, Fast and Flexible Pixiv Client +// Copyright (C) 2019-2020 Dylech30th +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as +// published by the Free Software Foundation, either version 3 of the +// License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see . + +#endregion + +using System; +using System.Collections.Generic; +using System.Collections.Immutable; +using System.Linq; +using Pixeval.Objects.Primitive; + +namespace Pixeval.Objects.Generic +{ + public static class Enumerates + { + public static bool IsNullOrEmpty(this IEnumerable enumerable) + { + return enumerable == null || !enumerable.Any(); + } + + public static void AddRange(this ICollection dst, IEnumerable src) + { + foreach (var t in src) + { + dst.Add(t); + } + } + + public static void AddSorted(this IList list, T item, IComparer comparer) + { + if (comparer == null) + { + list.Add(item); + return; + } + + var i = 0; + while (i < list.Count && comparer.Compare(list[i], item) < 0) + { + i++; + } + + list.Insert(i, item); + } + + public static IImmutableSet ToImmutableSet(this IEnumerable enumerable, Func function) + { + return enumerable == null ? new HashSet().ToImmutableHashSet() : enumerable.Select(function).ToImmutableHashSet(); + } + + public static bool EqualsIgnoreCase(this IEnumerable src, IEnumerable compare) + { + return src.All(x => Strings.IsNullOrEmpty(x) && compare.Any(i => i.EqualsIgnoreCase(x))); + } + + public static IEnumerable Peek(this IEnumerable enumerable, Action action) + { + var peek = enumerable as T[] ?? enumerable.ToArray(); + foreach (var t in peek) + { + action(t); + } + + return peek; + } + + public static IEnumerable NonNull(this IEnumerable source) + { + return source.Where(s => s != null); + } + } +} \ No newline at end of file diff --git a/Pixeval/Objects/Generic/Functions.cs b/Pixeval/Objects/Generic/Functions.cs new file mode 100644 index 00000000..6f89d45a --- /dev/null +++ b/Pixeval/Objects/Generic/Functions.cs @@ -0,0 +1,53 @@ +#region Copyright (C) 2019-2020 Dylech30th. All rights reserved. + +// Pixeval - A Strong, Fast and Flexible Pixiv Client +// Copyright (C) 2019-2020 Dylech30th +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as +// published by the Free Software Foundation, either version 3 of the +// License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see . + +#endregion + +using System; +using System.Threading.Tasks; + +namespace Pixeval.Objects.Generic +{ + public static class Functions + { + public static T Apply(this T receiver, Action action) + { + action(receiver); + return receiver; + } + + public static Task AwaitAsync(this T obj, Func> on, int interval = 0, TimeSpan timeout = default) + { + var timer = DateTime.Now; + return Task.Run(async () => + { + while (!await on(obj)) + { + if (timeout != default && DateTime.Now - timer >= timeout) + { + break; + } + if (interval != 0) + { + await Task.Delay(interval); + } + } + }); + } + } +} \ No newline at end of file diff --git a/Pixeval/Objects/Generic/Observable.cs b/Pixeval/Objects/Generic/Observable.cs new file mode 100644 index 00000000..9db014de --- /dev/null +++ b/Pixeval/Objects/Generic/Observable.cs @@ -0,0 +1,64 @@ +#region Copyright (C) 2019-2020 Dylech30th. All rights reserved. + +// Pixeval - A Strong, Fast and Flexible Pixiv Client +// Copyright (C) 2019-2020 Dylech30th +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as +// published by the Free Software Foundation, either version 3 of the +// License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see . + +#endregion + +using System; +using PropertyChanged; + +namespace Pixeval.Objects.Generic +{ + [AddINotifyPropertyChangedInterface] + public class Observable + { + private T value; + + public Observable(T value) + { + this.value = value; + } + + public T Value + { + get => value; + set + { + if (!this.value.Equals(value)) + { + ValueChanged?.Invoke(Value, new ObservableValueChangedEventArgs(this.value, value)); + this.value = value; + } + } + } + + public event EventHandler> ValueChanged; + } + + public class ObservableValueChangedEventArgs : EventArgs + { + public ObservableValueChangedEventArgs(T oldValue, T newValue) + { + OldValue = oldValue; + NewValue = newValue; + } + + public T OldValue { get; set; } + + public T NewValue { get; set; } + } +} \ No newline at end of file diff --git a/Pixeval/Objects/Generic/Tasks.cs b/Pixeval/Objects/Generic/Tasks.cs new file mode 100644 index 00000000..45ff0dda --- /dev/null +++ b/Pixeval/Objects/Generic/Tasks.cs @@ -0,0 +1,66 @@ +#region Copyright (C) 2019-2020 Dylech30th. All rights reserved. + +// Pixeval - A Strong, Fast and Flexible Pixiv Client +// Copyright (C) 2019-2020 Dylech30th +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as +// published by the Free Software Foundation, either version 3 of the +// License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see . + +#endregion + +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; + +namespace Pixeval.Objects.Generic +{ + public class Tasks + { + private Func> mappingFunc; + private IEnumerable taskQueue; + + private Tasks() + { + } + + public static Tasks Of(IEnumerable tasks) + { + return new Tasks { taskQueue = tasks.NonNull() }; + } + + public Tasks Mapping(Func> map) + { + mappingFunc = map; + return this; + } + + public IEnumerable> Construct() + { + return taskQueue.Select(taskObj => mappingFunc(taskObj)); + } + } + + public static class TaskHelper + { + public static Task WhenAll(this IEnumerable> tasks) + { + return Task.WhenAll(tasks); + } + + public static Task> WhenAny(this IEnumerable> tasks) + { + return Task.WhenAny(tasks); + } + } +} \ No newline at end of file diff --git a/Pixeval/Objects/I18n/ResourceLocator.cs b/Pixeval/Objects/I18n/ResourceLocator.cs new file mode 100644 index 00000000..7c127330 --- /dev/null +++ b/Pixeval/Objects/I18n/ResourceLocator.cs @@ -0,0 +1,100 @@ +#region Copyright (C) 2019-2020 Dylech30th. All rights reserved. + +// Pixeval - A Strong, Fast and Flexible Pixiv Client +// Copyright (C) 2019-2020 Dylech30th +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as +// published by the Free Software Foundation, either version 3 of the +// License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see . + +#endregion + +using System; +using System.Collections.Generic; +using System.Globalization; +using System.IO; +using System.Linq; +using System.Reflection; +using System.Windows; +using System.Windows.Resources; +using System.Xml; +using Pixeval.Data.ViewModel; +using Pixeval.Objects.Primitive; +using Pixeval.Persisting; + +namespace Pixeval.Objects.I18n +{ + public static partial class AkaI18N + { + private static IEnumerable _i18NXmlNodes; + + public static string GetResource(string key) + { + if (_i18NXmlNodes == null) + { + // we load settings here because we need to get the culture field + if (File.Exists(Path.Combine(AppContext.SettingsFolder, "settings.json"))) + { + var s = File.ReadAllText(Path.Combine(AppContext.SettingsFolder, "settings.json")).FromJson(); + InitializeXmlNodes(s.Culture); + } + else + { + InitializeXmlNodes(CultureInfo.CurrentCulture.Name); + } + } + + return GetValueByKey(key); + } + + public static void Reload(I18NOption option) + { + if (option == null || option.Name == CultureInfo.CurrentCulture.Name) + { + return; + } + InitializeXmlNodes(option.Name); + foreach (var prop in typeof(AkaI18N).GetProperties(BindingFlags.Public | BindingFlags.Static)) + { + prop.SetValue(null, GetValueByKey(prop.Name)); + } + } + + private static void InitializeXmlNodes(string culture) + { + var fileName = new Uri($"pack://application:,,,/Pixeval;component/Resources/resx_{culture.ToLower()}.xml"); + StreamResourceInfo streamResourceInfo; + try + { + streamResourceInfo = Application.GetResourceStream(fileName); + } + catch (IOException) + { + streamResourceInfo = Application.GetResourceStream(new Uri("pack://application:,,,/Pixeval;component/Resources/resx_zh-cn.xml")); + } + + var xml = new XmlDocument(); + xml.Load(streamResourceInfo?.Stream!); + _i18NXmlNodes = xml.SelectSingleNode("/localization")!.ChildNodes.Cast(); + } + + public static string GetCultureAcceptLanguage() + { + return Settings.Global.Culture.IsNullOrEmpty() ? CultureInfo.CurrentCulture.Name.ToLower() : Settings.Global.Culture; + } + + private static string GetValueByKey(string key) + { + return _i18NXmlNodes.First(p => p.Attributes!["key"].Value == key).Attributes!["value"].Value; + } + } +} \ No newline at end of file diff --git a/Pixeval/Objects/I18n/StringResources.cs b/Pixeval/Objects/I18n/StringResources.cs new file mode 100644 index 00000000..8f8e199b --- /dev/null +++ b/Pixeval/Objects/I18n/StringResources.cs @@ -0,0 +1,1741 @@ +#region Copyright (C) 2019-2020 Dylech30th. All rights reserved. + +// Pixeval - A Strong, Fast and Flexible Pixiv Client +// Copyright (C) 2019-2020 Dylech30th +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as +// published by the Free Software Foundation, either version 3 of the +// License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see . + +#endregion + +using System.ComponentModel; +using System.Runtime.CompilerServices; + +namespace Pixeval.Objects.I18n +{ + public static partial class AkaI18N + { + private static string _emptyEmailOrPasswordIsNotAllowed = GetResource(nameof(EmptyEmailOrPasswordIsNotAllowed)); + private static string _idDoNotExists = GetResource(nameof(IdDoNotExists)); + private static string _cannotFindUser = GetResource(nameof(CannotFindUser)); + private static string _inputIsEmpty = GetResource(nameof(InputIsEmpty)); + private static string _queryNotResponding = GetResource(nameof(QueryNotResponding)); + private static string _idIllegal = GetResource(nameof(IdIllegal)); + private static string _userIdIllegal = GetResource(nameof(UserIdIllegal)); + private static string _appApiAuthenticateTimeout = GetResource(nameof(AppApiAuthenticateTimeout)); + private static string _webApiAuthenticateTimeout = GetResource(nameof(WebApiAuthenticateTimeout)); + private static string _multiplePixevalInstanceDetected = GetResource(nameof(MultiplePixevalInstanceDetected)); + private static string _multiplePixevalInstanceDetectedTitle = GetResource(nameof(MultiplePixevalInstanceDetectedTitle)); + private static string _cppRedistributableRequired = GetResource(nameof(CppRedistributableRequired)); + private static string _cppRedistributableRequiredTitle = GetResource(nameof(CppRedistributableRequiredTitle)); + private static string _certificateInstallationIsRequired = GetResource(nameof(CertificateInstallationIsRequired)); + private static string _certificateInstallationIsRequiredTitle = GetResource(nameof(CertificateInstallationIsRequiredTitle)); + private static string _trendsAddIllust = GetResource(nameof(TrendsAddIllust)); + private static string _trendsAddBookmark = GetResource(nameof(TrendsAddBookmark)); + private static string _trendsAddFavorite = GetResource(nameof(TrendsAddFavorite)); + private static string _searchingTrends = GetResource(nameof(SearchingTrends)); + private static string _searchingUserUpdates = GetResource(nameof(SearchingUserUpdates)); + private static string _searchingGallery = GetResource(nameof(SearchingGallery)); + private static string _searchingRecommend = GetResource(nameof(SearchingRecommend)); + private static string _searchingFollower = GetResource(nameof(SearchingFollower)); + private static string _searchingSpotlight = GetResource(nameof(SearchingSpotlight)); + private static string _queuedDownload = GetResource(nameof(QueuedDownload)); + private static string _queuedAllToDownload = GetResource(nameof(QueuedAllToDownload)); + private static string _shareLinkCopiedToClipboard = GetResource(nameof(ShareLinkCopiedToClipboard)); + private static string _pathNotExist = GetResource(nameof(PathNotExist)); + private static string _sauceNaoFileCountLimit = GetResource(nameof(SauceNaoFileCountLimit)); + private static string _cannotFindResult = GetResource(nameof(CannotFindResult)); + private static string _pleaseSelectFile = GetResource(nameof(PleaseSelectFile)); + private static string _pleaseSelectLocation = GetResource(nameof(PleaseSelectLocation)); + private static string _cannotRetrieveContentLengthHeader = GetResource(nameof(CannotRetrieveContentLengthHeader)); + private static string _toggleR18OnSuccess = GetResource(nameof(ToggleR18OnSuccess)); + private static string _toggleR18OnFailed = GetResource(nameof(ToggleR18OnFailed)); + private static string _tryingToToggleR18Switch = GetResource(nameof(TryingToToggleR18Switch)); + private static string _rankOptionDay = GetResource(nameof(RankOptionDay)); + private static string _rankOptionWeek = GetResource(nameof(RankOptionWeek)); + private static string _rankOptionMonth = GetResource(nameof(RankOptionMonth)); + private static string _rankOptionDayMale = GetResource(nameof(RankOptionDayMale)); + private static string _rankOptionDayFemale = GetResource(nameof(RankOptionDayFemale)); + private static string _rankOptionDayManga = GetResource(nameof(RankOptionDayManga)); + private static string _rankOptionWeekManga = GetResource(nameof(RankOptionWeekManga)); + private static string _rankOptionWeekOriginal = GetResource(nameof(RankOptionWeekOriginal)); + private static string _rankOptionWeekRookie = GetResource(nameof(RankOptionWeekRookie)); + private static string _rankOptionDayR18 = GetResource(nameof(RankOptionDayR18)); + private static string _rankOptionDayMaleR18 = GetResource(nameof(RankOptionDayMaleR18)); + private static string _rankOptionDayFemaleR18 = GetResource(nameof(RankOptionDayFemaleR18)); + private static string _rankOptionWeekR18 = GetResource(nameof(RankOptionWeekR18)); + private static string _rankOptionWeekR18G = GetResource(nameof(RankOptionWeekR18G)); + private static string _rankDateCannotBeNull = GetResource(nameof(RankDateCannotBeNull)); + private static string _rankNeedR18On = GetResource(nameof(RankNeedR18On)); + private static string _cannotFindSpecifiedCertificate = GetResource(nameof(CannotFindSpecifiedCertificate)); + private static string _gifIllustrationHint = GetResource(nameof(GifIllustrationHint)); + private static string _mangaIllustrationHintFormat = GetResource(nameof(MangaIllustrationHintFormat)); + private static string _userIdHintFormat = GetResource(nameof(UserIdHintFormat)); + private static string _downloadSingleIllustration = GetResource(nameof(DownloadSingleIllustration)); + private static string _downloadAllInCurrentList = GetResource(nameof(DownloadAllInCurrentList)); + private static string _downloadSpotlight = GetResource(nameof(DownloadSpotlight)); + private static string _retractSidebar = GetResource(nameof(RetractSidebar)); + private static string _restrictPolicy = GetResource(nameof(RestrictPolicy)); + private static string _private = GetResource(nameof(Private)); + private static string _public = GetResource(nameof(Public)); + private static string _homePage = GetResource(nameof(HomePage)); + private static string _myGallery = GetResource(nameof(MyGallery)); + private static string _myFollowing = GetResource(nameof(MyFollowing)); + private static string _spotlight = GetResource(nameof(Spotlight)); + private static string _recommend = GetResource(nameof(Recommend)); + private static string _illustRanking = GetResource(nameof(IllustRanking)); + private static string _userTrend = GetResource(nameof(UserTrend)); + private static string _userUpdate = GetResource(nameof(UserUpdate)); + private static string _searchImageBySource = GetResource(nameof(SearchImageBySource)); + private static string _downloadQueueAndHistory = GetResource(nameof(DownloadQueueAndHistory)); + private static string _setting = GetResource(nameof(Setting)); + private static string _logout = GetResource(nameof(Logout)); + private static string _searchHint = GetResource(nameof(SearchHint)); + private static string _queryUser = GetResource(nameof(QueryUser)); + private static string _querySingleUser = GetResource(nameof(QuerySingleUser)); + private static string _querySingleIllust = GetResource(nameof(QuerySingleIllust)); + private static string _recommendIllustratorTurnPage = GetResource(nameof(RecommendIllustratorTurnPage)); + private static string _pixevalVersionFormat = GetResource(nameof(PixevalVersionFormat)); + private static string _aboutPixeval = GetResource(nameof(AboutPixeval)); + private static string _userBrowserFollowCountHint = GetResource(nameof(UserBrowserFollowCountHint)); + private static string _userBrowserFollow = GetResource(nameof(UserBrowserFollow)); + private static string _userBrowserUnFollow = GetResource(nameof(UserBrowserUnFollow)); + private static string _userBrowserPrivateFollow = GetResource(nameof(UserBrowserPrivateFollow)); + private static string _userBrowserIllustSelector = GetResource(nameof(UserBrowserIllustSelector)); + private static string _userBrowserGallerySelector = GetResource(nameof(UserBrowserGallerySelector)); + private static string _illustBrowserIllustId = GetResource(nameof(IllustBrowserIllustId)); + private static string _illustBrowserTotalViews = GetResource(nameof(IllustBrowserTotalViews)); + private static string _illustBrowserTotalBookmarks = GetResource(nameof(IllustBrowserTotalBookmarks)); + private static string _illustBrowserResolution = GetResource(nameof(IllustBrowserResolution)); + private static string _illustBrowserUploadDate = GetResource(nameof(IllustBrowserUploadDate)); + private static string _illustBrowserIllustTag = GetResource(nameof(IllustBrowserIllustTag)); + private static string _illustBrowserSetWallpaper = GetResource(nameof(IllustBrowserSetWallpaper)); + private static string _illustBrowserShareLink = GetResource(nameof(IllustBrowserShareLink)); + private static string _illustBrowserViewInBrowser = GetResource(nameof(IllustBrowserViewInBrowser)); + private static string _illustBrowserDownload = GetResource(nameof(IllustBrowserDownload)); + private static string _illustBrowserPrivateBookmark = GetResource(nameof(IllustBrowserPrivateBookmark)); + private static string _illustBrowserBookmark = GetResource(nameof(IllustBrowserBookmark)); + private static string _illustBrowserRemoveBookmark = GetResource(nameof(IllustBrowserRemoveBookmark)); + private static string _downloadQueueShowDownloadIllust = GetResource(nameof(DownloadQueueShowDownloadIllust)); + private static string _downloadQueueRemoveFromDownloading = GetResource(nameof(DownloadQueueRemoveFromDownloading)); + private static string _downloadQueueDownloading = GetResource(nameof(DownloadQueueDownloading)); + private static string _downloadQueueEmptyNotifier = GetResource(nameof(DownloadQueueEmptyNotifier)); + private static string _downloadQueueDownloaded = GetResource(nameof(DownloadQueueDownloaded)); + private static string _emailOrPasswordIsWrong = GetResource(nameof(EmailOrPasswordIsWrong)); + private static string _copy = GetResource(nameof(Copy)); + private static string _conditionBoxHint = GetResource(nameof(ConditionBoxHint)); + private static string _pixevalSettings = GetResource(nameof(PixevalSettings)); + private static string _sortByPopulation = GetResource(nameof(SortByPopulation)); + private static string _turnOffR18 = GetResource(nameof(TurnOffR18)); + private static string _turnOnIllustratorRecommend = GetResource(nameof(TurnOnIllustratorRecommend)); + private static string _turnOnDirectConnect = GetResource(nameof(TurnOnDirectConnect)); + private static string _tagMatchOption = GetResource(nameof(TagMatchOption)); + private static string _turnOnCache = GetResource(nameof(TurnOnCache)); + private static string _memoryCachePolicy = GetResource(nameof(MemoryCachePolicy)); + private static string _fileCachePolicy = GetResource(nameof(FileCachePolicy)); + private static string _minBookmarkRequired = GetResource(nameof(MinBookmarkRequired)); + private static string _searchPageCountHint = GetResource(nameof(SearchPageCountHint)); + private static string _searchPageStart = GetResource(nameof(SearchPageStart)); + private static string _spotlightSearchPageStart = GetResource(nameof(SpotlightSearchPageStart)); + private static string _downloadLocation = GetResource(nameof(DownloadLocation)); + private static string _tagsToBeExclude = GetResource(nameof(TagsToBeExclude)); + private static string _tagsToBeInclude = GetResource(nameof(TagsToBeInclude)); + private static string _searchPerPageCountHint = GetResource(nameof(SearchPerPageCountHint)); + private static string _turnOnWebR18 = GetResource(nameof(TurnOnWebR18)); + private static string _sauceNaoFileLocationHint = GetResource(nameof(SauceNaoFileLocationHint)); + private static string _sauceNaoUploadAndSearch = GetResource(nameof(SauceNaoUploadAndSearch)); + private static string _signIn = GetResource(nameof(SignIn)); + private static string _signInAccount = GetResource(nameof(SignInAccount)); + private static string _signInPassword = GetResource(nameof(SignInPassword)); + private static string _signInButtonText = GetResource(nameof(SignInButtonText)); + private static string _signInUpdatingSession = GetResource(nameof(SignInUpdatingSession)); + private static string _browsingHistoryCount = GetResource(nameof(BrowsingHistoryCount)); + private static string _downloadQueueBrowsingHistory = GetResource(nameof(DownloadQueueBrowsingHistory)); + private static string _downloadQueueHistoryListIsEmpty = GetResource(nameof(DownloadQueueHistoryListIsEmpty)); + private static string _pixevalUpdateAvailable = GetResource(nameof(PixevalUpdateAvailable)); + private static string _pixevalUpdateAvailableTitle = GetResource(nameof(PixevalUpdateAvailableTitle)); + private static string _thisLoginSessionRequiresRecaptcha = GetResource(nameof(ThisLoginSessionRequiresRecaptcha)); + private static string _supportMe = GetResource(nameof(SupportMe)); + private static string _downloadTo = GetResource(nameof(DownloadTo)); + private static string _userPreviewPopupFollow = GetResource(nameof(UserPreviewPopupFollow)); + private static string _userPreviewPopupUnFollow = GetResource(nameof(UserPreviewPopupUnFollow)); + private static string _selectCultureInfo = GetResource(nameof(SelectCultureInfo)); + private static string _createNewFolderWhenDownloadFromUser = GetResource(nameof(CreateNewFolderWhenDownloadFromUser)); + private static string _requiresWebCookie = GetResource(nameof(RequiresWebCookie)); + private static string _yes = GetResource(nameof(Yes)); + private static string _no = GetResource(nameof(No)); + private static string _warning = GetResource(nameof(Warning)); + private static string _fillWithWebCookie = GetResource(nameof(FillWithWebCookie)); + private static string _imageMirrorServerUrl = GetResource(nameof(ImageMirrorServerUrl)); + private static string _imageMirrorServerUrlHint = GetResource(nameof(ImageMirrorServerUrlHint)); + private static string _conditionBoxTooltip = GetResource(nameof(ConditionBoxTooltip)); + private static string _batchDownloadAcknowledgment = GetResource(nameof(BatchDownloadAcknowledgment)); + + public static string EmptyEmailOrPasswordIsNotAllowed + { + get => _emptyEmailOrPasswordIsNotAllowed; + set + { + _emptyEmailOrPasswordIsNotAllowed = value; + OnStaticPropertyChanged(); + } + } + + public static string IdDoNotExists + { + get => _idDoNotExists; + set + { + _idDoNotExists = value; + OnStaticPropertyChanged(); + } + } + + public static string CannotFindUser + { + get => _cannotFindUser; + set + { + _cannotFindUser = value; + OnStaticPropertyChanged(); + } + } + + public static string InputIsEmpty + { + get => _inputIsEmpty; + set + { + _inputIsEmpty = value; + OnStaticPropertyChanged(); + } + } + + public static string QueryNotResponding + { + get => _queryNotResponding; + set + { + _queryNotResponding = value; + OnStaticPropertyChanged(); + } + } + + public static string IdIllegal + { + get => _idIllegal; + set + { + _idIllegal = value; + OnStaticPropertyChanged(); + } + } + + public static string UserIdIllegal + { + get => _userIdIllegal; + set + { + _userIdIllegal = value; + OnStaticPropertyChanged(); + } + } + + public static string AppApiAuthenticateTimeout + { + get => _appApiAuthenticateTimeout; + set + { + _appApiAuthenticateTimeout = value; + OnStaticPropertyChanged(); + } + } + + public static string WebApiAuthenticateTimeout + { + get => _webApiAuthenticateTimeout; + set + { + _webApiAuthenticateTimeout = value; + OnStaticPropertyChanged(); + } + } + + public static string MultiplePixevalInstanceDetected + { + get => _multiplePixevalInstanceDetected; + set + { + _multiplePixevalInstanceDetected = value; + OnStaticPropertyChanged(); + } + } + + public static string MultiplePixevalInstanceDetectedTitle + { + get => _multiplePixevalInstanceDetectedTitle; + set + { + _multiplePixevalInstanceDetectedTitle = value; + OnStaticPropertyChanged(); + } + } + + public static string CppRedistributableRequired + { + get => _cppRedistributableRequired; + set + { + _cppRedistributableRequired = value; + OnStaticPropertyChanged(); + } + } + + public static string CppRedistributableRequiredTitle + { + get => _cppRedistributableRequiredTitle; + set + { + _cppRedistributableRequiredTitle = value; + OnStaticPropertyChanged(); + } + } + + public static string CertificateInstallationIsRequired + { + get => _certificateInstallationIsRequired; + set + { + _certificateInstallationIsRequired = value; + OnStaticPropertyChanged(); + } + } + + public static string CertificateInstallationIsRequiredTitle + { + get => _certificateInstallationIsRequiredTitle; + set + { + _certificateInstallationIsRequiredTitle = value; + OnStaticPropertyChanged(); + } + } + + public static string TrendsAddIllust + { + get => _trendsAddIllust; + set + { + _trendsAddIllust = value; + OnStaticPropertyChanged(); + } + } + + public static string TrendsAddBookmark + { + get => _trendsAddBookmark; + set + { + _trendsAddBookmark = value; + OnStaticPropertyChanged(); + } + } + + public static string TrendsAddFavorite + { + get => _trendsAddFavorite; + set + { + _trendsAddFavorite = value; + OnStaticPropertyChanged(); + } + } + + public static string SearchingTrends + { + get => _searchingTrends; + set + { + _searchingTrends = value; + OnStaticPropertyChanged(); + } + } + + public static string SearchingUserUpdates + { + get => _searchingUserUpdates; + set + { + _searchingUserUpdates = value; + OnStaticPropertyChanged(); + } + } + + public static string SearchingGallery + { + get => _searchingGallery; + set + { + _searchingGallery = value; + OnStaticPropertyChanged(); + } + } + + public static string SearchingRecommend + { + get => _searchingRecommend; + set + { + _searchingRecommend = value; + OnStaticPropertyChanged(); + } + } + + public static string SearchingFollower + { + get => _searchingFollower; + set + { + _searchingFollower = value; + OnStaticPropertyChanged(); + } + } + + public static string SearchingSpotlight + { + get => _searchingSpotlight; + set + { + _searchingSpotlight = value; + OnStaticPropertyChanged(); + } + } + + public static string QueuedDownload + { + get => _queuedDownload; + set + { + _queuedDownload = value; + OnStaticPropertyChanged(); + } + } + + public static string QueuedAllToDownload + { + get => _queuedAllToDownload; + set + { + _queuedAllToDownload = value; + OnStaticPropertyChanged(); + } + } + + public static string ShareLinkCopiedToClipboard + { + get => _shareLinkCopiedToClipboard; + set + { + _shareLinkCopiedToClipboard = value; + OnStaticPropertyChanged(); + } + } + + public static string PathNotExist + { + get => _pathNotExist; + set + { + _pathNotExist = value; + OnStaticPropertyChanged(); + } + } + + public static string SauceNaoFileCountLimit + { + get => _sauceNaoFileCountLimit; + set + { + _sauceNaoFileCountLimit = value; + OnStaticPropertyChanged(); + } + } + + public static string CannotFindResult + { + get => _cannotFindResult; + set + { + _cannotFindResult = value; + OnStaticPropertyChanged(); + } + } + + public static string PleaseSelectFile + { + get => _pleaseSelectFile; + set + { + _pleaseSelectFile = value; + OnStaticPropertyChanged(); + } + } + + public static string PleaseSelectLocation + { + get => _pleaseSelectLocation; + set + { + _pleaseSelectLocation = value; + OnStaticPropertyChanged(); + } + } + + public static string CannotRetrieveContentLengthHeader + { + get => _cannotRetrieveContentLengthHeader; + set + { + _cannotRetrieveContentLengthHeader = value; + OnStaticPropertyChanged(); + } + } + + public static string ToggleR18OnSuccess + { + get => _toggleR18OnSuccess; + set + { + _toggleR18OnSuccess = value; + OnStaticPropertyChanged(); + } + } + + public static string ToggleR18OnFailed + { + get => _toggleR18OnFailed; + set + { + _toggleR18OnFailed = value; + OnStaticPropertyChanged(); + } + } + + public static string TryingToToggleR18Switch + { + get => _tryingToToggleR18Switch; + set + { + _tryingToToggleR18Switch = value; + OnStaticPropertyChanged(); + } + } + + public static string RankOptionDay + { + get => _rankOptionDay; + set + { + _rankOptionDay = value; + OnStaticPropertyChanged(); + } + } + + public static string RankOptionWeek + { + get => _rankOptionWeek; + set + { + _rankOptionWeek = value; + OnStaticPropertyChanged(); + } + } + + public static string RankOptionMonth + { + get => _rankOptionMonth; + set + { + _rankOptionMonth = value; + OnStaticPropertyChanged(); + } + } + + public static string RankOptionDayMale + { + get => _rankOptionDayMale; + set + { + _rankOptionDayMale = value; + OnStaticPropertyChanged(); + } + } + + public static string RankOptionDayFemale + { + get => _rankOptionDayFemale; + set + { + _rankOptionDayFemale = value; + OnStaticPropertyChanged(); + } + } + + public static string RankOptionDayManga + { + get => _rankOptionDayManga; + set + { + _rankOptionDayManga = value; + OnStaticPropertyChanged(); + } + } + + public static string RankOptionWeekManga + { + get => _rankOptionWeekManga; + set + { + _rankOptionWeekManga = value; + OnStaticPropertyChanged(); + } + } + + public static string RankOptionWeekOriginal + { + get => _rankOptionWeekOriginal; + set + { + _rankOptionWeekOriginal = value; + OnStaticPropertyChanged(); + } + } + + public static string RankOptionWeekRookie + { + get => _rankOptionWeekRookie; + set + { + _rankOptionWeekRookie = value; + OnStaticPropertyChanged(); + } + } + + public static string RankOptionDayR18 + { + get => _rankOptionDayR18; + set + { + _rankOptionDayR18 = value; + OnStaticPropertyChanged(); + } + } + + public static string RankOptionDayMaleR18 + { + get => _rankOptionDayMaleR18; + set + { + _rankOptionDayMaleR18 = value; + OnStaticPropertyChanged(); + } + } + + public static string RankOptionDayFemaleR18 + { + get => _rankOptionDayFemaleR18; + set + { + _rankOptionDayFemaleR18 = value; + OnStaticPropertyChanged(); + } + } + + public static string RankOptionWeekR18 + { + get => _rankOptionWeekR18; + set + { + _rankOptionWeekR18 = value; + OnStaticPropertyChanged(); + } + } + + public static string RankOptionWeekR18G + { + get => _rankOptionWeekR18G; + set + { + _rankOptionWeekR18G = value; + OnStaticPropertyChanged(); + } + } + + public static string RankDateCannotBeNull + { + get => _rankDateCannotBeNull; + set + { + _rankDateCannotBeNull = value; + OnStaticPropertyChanged(); + } + } + + public static string RankNeedR18On + { + get => _rankNeedR18On; + set + { + _rankNeedR18On = value; + OnStaticPropertyChanged(); + } + } + + public static string CannotFindSpecifiedCertificate + { + get => _cannotFindSpecifiedCertificate; + set + { + _cannotFindSpecifiedCertificate = value; + OnStaticPropertyChanged(); + } + } + + public static string GifIllustrationHint + { + get => _gifIllustrationHint; + set + { + _gifIllustrationHint = value; + OnStaticPropertyChanged(); + } + } + + public static string MangaIllustrationHintFormat + { + get => _mangaIllustrationHintFormat; + set + { + _mangaIllustrationHintFormat = value; + OnStaticPropertyChanged(); + } + } + + public static string UserIdHintFormat + { + get => _userIdHintFormat; + set + { + _userIdHintFormat = value; + OnStaticPropertyChanged(); + } + } + + public static string DownloadSingleIllustration + { + get => _downloadSingleIllustration; + set + { + _downloadSingleIllustration = value; + OnStaticPropertyChanged(); + } + } + + public static string DownloadAllInCurrentList + { + get => _downloadAllInCurrentList; + set + { + _downloadAllInCurrentList = value; + OnStaticPropertyChanged(); + } + } + + public static string DownloadSpotlight + { + get => _downloadSpotlight; + set + { + _downloadSpotlight = value; + OnStaticPropertyChanged(); + } + } + + public static string RetractSidebar + { + get => _retractSidebar; + set + { + _retractSidebar = value; + OnStaticPropertyChanged(); + } + } + + public static string RestrictPolicy + { + get => _restrictPolicy; + set + { + _restrictPolicy = value; + OnStaticPropertyChanged(); + } + } + + public static string Private + { + get => _private; + set + { + _private = value; + OnStaticPropertyChanged(); + } + } + + public static string Public + { + get => _public; + set + { + _public = value; + OnStaticPropertyChanged(); + } + } + + public static string HomePage + { + get => _homePage; + set + { + _homePage = value; + OnStaticPropertyChanged(); + } + } + + public static string MyGallery + { + get => _myGallery; + set + { + _myGallery = value; + OnStaticPropertyChanged(); + } + } + + public static string MyFollowing + { + get => _myFollowing; + set + { + _myFollowing = value; + OnStaticPropertyChanged(); + } + } + + public static string Spotlight + { + get => _spotlight; + set + { + _spotlight = value; + OnStaticPropertyChanged(); + } + } + + public static string Recommend + { + get => _recommend; + set + { + _recommend = value; + OnStaticPropertyChanged(); + } + } + + public static string IllustRanking + { + get => _illustRanking; + set + { + _illustRanking = value; + OnStaticPropertyChanged(); + } + } + + public static string UserTrend + { + get => _userTrend; + set + { + _userTrend = value; + OnStaticPropertyChanged(); + } + } + + public static string UserUpdate + { + get => _userUpdate; + set + { + _userUpdate = value; + OnStaticPropertyChanged(); + } + } + + public static string SearchImageBySource + { + get => _searchImageBySource; + set + { + _searchImageBySource = value; + OnStaticPropertyChanged(); + } + } + + public static string DownloadQueueAndHistory + { + get => _downloadQueueAndHistory; + set + { + _downloadQueueAndHistory = value; + OnStaticPropertyChanged(); + } + } + + public static string Setting + { + get => _setting; + set + { + _setting = value; + OnStaticPropertyChanged(); + } + } + + public static string Logout + { + get => _logout; + set + { + _logout = value; + OnStaticPropertyChanged(); + } + } + + public static string SearchHint + { + get => _searchHint; + set + { + _searchHint = value; + OnStaticPropertyChanged(); + } + } + + public static string QueryUser + { + get => _queryUser; + set + { + _queryUser = value; + OnStaticPropertyChanged(); + } + } + + public static string QuerySingleUser + { + get => _querySingleUser; + set + { + _querySingleUser = value; + OnStaticPropertyChanged(); + } + } + + public static string QuerySingleIllust + { + get => _querySingleIllust; + set + { + _querySingleIllust = value; + OnStaticPropertyChanged(); + } + } + + public static string RecommendIllustratorTurnPage + { + get => _recommendIllustratorTurnPage; + set + { + _recommendIllustratorTurnPage = value; + OnStaticPropertyChanged(); + } + } + + public static string PixevalVersionFormat + { + get => _pixevalVersionFormat; + set + { + _pixevalVersionFormat = value; + OnStaticPropertyChanged(); + } + } + + public static string AboutPixeval + { + get => _aboutPixeval; + set + { + _aboutPixeval = value; + OnStaticPropertyChanged(); + } + } + + public static string UserBrowserFollowCountHint + { + get => _userBrowserFollowCountHint; + set + { + _userBrowserFollowCountHint = value; + OnStaticPropertyChanged(); + } + } + + public static string UserBrowserFollow + { + get => _userBrowserFollow; + set + { + _userBrowserFollow = value; + OnStaticPropertyChanged(); + } + } + + public static string UserBrowserUnFollow + { + get => _userBrowserUnFollow; + set + { + _userBrowserUnFollow = value; + OnStaticPropertyChanged(); + } + } + + public static string UserBrowserPrivateFollow + { + get => _userBrowserPrivateFollow; + set + { + _userBrowserPrivateFollow = value; + OnStaticPropertyChanged(); + } + } + + public static string UserBrowserIllustSelector + { + get => _userBrowserIllustSelector; + set + { + _userBrowserIllustSelector = value; + OnStaticPropertyChanged(); + } + } + + public static string UserBrowserGallerySelector + { + get => _userBrowserGallerySelector; + set + { + _userBrowserGallerySelector = value; + OnStaticPropertyChanged(); + } + } + + public static string IllustBrowserIllustId + { + get => _illustBrowserIllustId; + set + { + _illustBrowserIllustId = value; + OnStaticPropertyChanged(); + } + } + + public static string IllustBrowserTotalViews + { + get => _illustBrowserTotalViews; + set + { + _illustBrowserTotalViews = value; + OnStaticPropertyChanged(); + } + } + + public static string IllustBrowserTotalBookmarks + { + get => _illustBrowserTotalBookmarks; + set + { + _illustBrowserTotalBookmarks = value; + OnStaticPropertyChanged(); + } + } + + public static string IllustBrowserResolution + { + get => _illustBrowserResolution; + set + { + _illustBrowserResolution = value; + OnStaticPropertyChanged(); + } + } + + public static string IllustBrowserUploadDate + { + get => _illustBrowserUploadDate; + set + { + _illustBrowserUploadDate = value; + OnStaticPropertyChanged(); + } + } + + public static string IllustBrowserIllustTag + { + get => _illustBrowserIllustTag; + set + { + _illustBrowserIllustTag = value; + OnStaticPropertyChanged(); + } + } + + public static string IllustBrowserSetWallpaper + { + get => _illustBrowserSetWallpaper; + set + { + _illustBrowserSetWallpaper = value; + OnStaticPropertyChanged(); + } + } + + public static string IllustBrowserShareLink + { + get => _illustBrowserShareLink; + set + { + _illustBrowserShareLink = value; + OnStaticPropertyChanged(); + } + } + + public static string IllustBrowserViewInBrowser + { + get => _illustBrowserViewInBrowser; + set + { + _illustBrowserViewInBrowser = value; + OnStaticPropertyChanged(); + } + } + + public static string IllustBrowserDownload + { + get => _illustBrowserDownload; + set + { + _illustBrowserDownload = value; + OnStaticPropertyChanged(); + } + } + + public static string IllustBrowserPrivateBookmark + { + get => _illustBrowserPrivateBookmark; + set + { + _illustBrowserPrivateBookmark = value; + OnStaticPropertyChanged(); + } + } + + public static string IllustBrowserBookmark + { + get => _illustBrowserBookmark; + set + { + _illustBrowserBookmark = value; + OnStaticPropertyChanged(); + } + } + + public static string IllustBrowserRemoveBookmark + { + get => _illustBrowserRemoveBookmark; + set + { + _illustBrowserRemoveBookmark = value; + OnStaticPropertyChanged(); + } + } + + public static string DownloadQueueShowDownloadIllust + { + get => _downloadQueueShowDownloadIllust; + set + { + _downloadQueueShowDownloadIllust = value; + OnStaticPropertyChanged(); + } + } + + public static string DownloadQueueRemoveFromDownloading + { + get => _downloadQueueRemoveFromDownloading; + set + { + _downloadQueueRemoveFromDownloading = value; + OnStaticPropertyChanged(); + } + } + + public static string DownloadQueueDownloading + { + get => _downloadQueueDownloading; + set + { + _downloadQueueDownloading = value; + OnStaticPropertyChanged(); + } + } + + public static string DownloadQueueEmptyNotifier + { + get => _downloadQueueEmptyNotifier; + set + { + _downloadQueueEmptyNotifier = value; + OnStaticPropertyChanged(); + } + } + + public static string DownloadQueueDownloaded + { + get => _downloadQueueDownloaded; + set + { + _downloadQueueDownloaded = value; + OnStaticPropertyChanged(); + } + } + + public static string EmailOrPasswordIsWrong + { + get => _emailOrPasswordIsWrong; + set + { + _emailOrPasswordIsWrong = value; + OnStaticPropertyChanged(); + } + } + + public static string Copy + { + get => _copy; + set + { + _copy = value; + OnStaticPropertyChanged(); + } + } + + public static string ConditionBoxHint + { + get => _conditionBoxHint; + set + { + _conditionBoxHint = value; + OnStaticPropertyChanged(); + } + } + + public static string PixevalSettings + { + get => _pixevalSettings; + set + { + _pixevalSettings = value; + OnStaticPropertyChanged(); + } + } + + public static string SortByPopulation + { + get => _sortByPopulation; + set + { + _sortByPopulation = value; + OnStaticPropertyChanged(); + } + } + + public static string TurnOffR18 + { + get => _turnOffR18; + set + { + _turnOffR18 = value; + OnStaticPropertyChanged(); + } + } + + public static string TurnOnIllustratorRecommend + { + get => _turnOnIllustratorRecommend; + set + { + _turnOnIllustratorRecommend = value; + OnStaticPropertyChanged(); + } + } + + public static string TurnOnDirectConnect + { + get => _turnOnDirectConnect; + set + { + _turnOnDirectConnect = value; + OnStaticPropertyChanged(); + } + } + + public static string TagMatchOption + { + get => _tagMatchOption; + set + { + _tagMatchOption = value; + OnStaticPropertyChanged(); + } + } + + public static string TurnOnCache + { + get => _turnOnCache; + set + { + _turnOnCache = value; + OnStaticPropertyChanged(); + } + } + + public static string MemoryCachePolicy + { + get => _memoryCachePolicy; + set + { + _memoryCachePolicy = value; + OnStaticPropertyChanged(); + } + } + + public static string FileCachePolicy + { + get => _fileCachePolicy; + set + { + _fileCachePolicy = value; + OnStaticPropertyChanged(); + } + } + + public static string MinBookmarkRequired + { + get => _minBookmarkRequired; + set + { + _minBookmarkRequired = value; + OnStaticPropertyChanged(); + } + } + + public static string SearchPageCountHint + { + get => _searchPageCountHint; + set + { + _searchPageCountHint = value; + OnStaticPropertyChanged(); + } + } + + public static string SearchPageStart + { + get => _searchPageStart; + set + { + _searchPageStart = value; + OnStaticPropertyChanged(); + } + } + + public static string SpotlightSearchPageStart + { + get => _spotlightSearchPageStart; + set + { + _spotlightSearchPageStart = value; + OnStaticPropertyChanged(); + } + } + + public static string DownloadLocation + { + get => _downloadLocation; + set + { + _downloadLocation = value; + OnStaticPropertyChanged(); + } + } + + public static string TagsToBeExclude + { + get => _tagsToBeExclude; + set + { + _tagsToBeExclude = value; + OnStaticPropertyChanged(); + } + } + + public static string TagsToBeInclude + { + get => _tagsToBeInclude; + set + { + _tagsToBeInclude = value; + OnStaticPropertyChanged(); + } + } + + public static string SearchPerPageCountHint + { + get => _searchPerPageCountHint; + set + { + _searchPerPageCountHint = value; + OnStaticPropertyChanged(); + } + } + + public static string TurnOnWebR18 + { + get => _turnOnWebR18; + set + { + _turnOnWebR18 = value; + OnStaticPropertyChanged(); + } + } + + public static string SauceNaoFileLocationHint + { + get => _sauceNaoFileLocationHint; + set + { + _sauceNaoFileLocationHint = value; + OnStaticPropertyChanged(); + } + } + + public static string SauceNaoUploadAndSearch + { + get => _sauceNaoUploadAndSearch; + set + { + _sauceNaoUploadAndSearch = value; + OnStaticPropertyChanged(); + } + } + + public static string SignIn + { + get => _signIn; + set + { + _signIn = value; + OnStaticPropertyChanged(); + } + } + + public static string SignInAccount + { + get => _signInAccount; + set + { + _signInAccount = value; + OnStaticPropertyChanged(); + } + } + + public static string SignInPassword + { + get => _signInPassword; + set + { + _signInPassword = value; + OnStaticPropertyChanged(); + } + } + + public static string SignInButtonText + { + get => _signInButtonText; + set + { + _signInButtonText = value; + OnStaticPropertyChanged(); + } + } + + public static string SignInUpdatingSession + { + get => _signInUpdatingSession; + set + { + _signInUpdatingSession = value; + OnStaticPropertyChanged(); + } + } + + public static string BrowsingHistoryCount + { + get => _browsingHistoryCount; + set + { + _browsingHistoryCount = value; + OnStaticPropertyChanged(); + } + } + + public static string DownloadQueueBrowsingHistory + { + get => _downloadQueueBrowsingHistory; + set + { + _downloadQueueBrowsingHistory = value; + OnStaticPropertyChanged(); + } + } + + public static string DownloadQueueHistoryListIsEmpty + { + get => _downloadQueueHistoryListIsEmpty; + set + { + _downloadQueueHistoryListIsEmpty = value; + OnStaticPropertyChanged(); + } + } + + public static string PixevalUpdateAvailable + { + get => _pixevalUpdateAvailable; + set + { + _pixevalUpdateAvailable = value; + OnStaticPropertyChanged(); + } + } + + public static string PixevalUpdateAvailableTitle + { + get => _pixevalUpdateAvailableTitle; + set + { + _pixevalUpdateAvailableTitle = value; + OnStaticPropertyChanged(); + } + } + + public static string ThisLoginSessionRequiresRecaptcha + { + get => _thisLoginSessionRequiresRecaptcha; + set + { + _thisLoginSessionRequiresRecaptcha = value; + OnStaticPropertyChanged(); + } + } + + public static string SupportMe + { + get => _supportMe; + set + { + _supportMe = value; + OnStaticPropertyChanged(); + } + } + + public static string DownloadTo + { + get => _downloadTo; + set + { + _downloadTo = value; + OnStaticPropertyChanged(); + } + } + + public static string UserPreviewPopupFollow + { + get => _userPreviewPopupFollow; + set + { + _userPreviewPopupFollow = value; + OnStaticPropertyChanged(); + } + } + + public static string UserPreviewPopupUnFollow + { + get => _userPreviewPopupUnFollow; + set + { + _userPreviewPopupUnFollow = value; + OnStaticPropertyChanged(); + } + } + + public static string SelectCultureInfo + { + get => _selectCultureInfo; + set + { + _selectCultureInfo = value; + OnStaticPropertyChanged(); + } + } + + public static string CreateNewFolderWhenDownloadFromUser + { + get => _createNewFolderWhenDownloadFromUser; + set + { + _createNewFolderWhenDownloadFromUser = value; + OnStaticPropertyChanged(); + } + } + + public static string RequiresWebCookie + { + get => _requiresWebCookie; + set + { + _requiresWebCookie = value; + OnStaticPropertyChanged(); + } + } + + public static string Yes + { + get => _yes; + set + { + _yes = value; + OnStaticPropertyChanged(); + } + } + + public static string No + { + get => _no; + set + { + _no = value; + OnStaticPropertyChanged(); + } + } + + public static string Warning + { + get => _warning; + set + { + _warning = value; + OnStaticPropertyChanged(); + } + } + + public static string FillWithWebCookie + { + get => _fillWithWebCookie; + set + { + _fillWithWebCookie = value; + OnStaticPropertyChanged(); + } + } + + public static string ImageMirrorServerUrl + { + get => _imageMirrorServerUrl; + set + { + _imageMirrorServerUrl = value; + OnStaticPropertyChanged(); + } + } + + public static string ImageMirrorServerUrlHint + { + get => _imageMirrorServerUrlHint; + set + { + _imageMirrorServerUrlHint = value; + OnStaticPropertyChanged(); + } + } + + public static string ConditionBoxTooltip + { + get => _conditionBoxTooltip; + set + { + _conditionBoxTooltip = value; + OnStaticPropertyChanged(); + } + } + + public static string BatchDownloadAcknowledgment + { + get => _batchDownloadAcknowledgment; + set + { + _batchDownloadAcknowledgment = value; + OnStaticPropertyChanged(); + } + } + + public static event PropertyChangedEventHandler StaticPropertyChanged; + + private static void OnStaticPropertyChanged([CallerMemberName] string propertyName = null) + { + StaticPropertyChanged?.Invoke(null, new PropertyChangedEventArgs(propertyName)); + } + } +} \ No newline at end of file diff --git a/Pixeval/Objects/Native/WallpaperManager.cs b/Pixeval/Objects/Native/WallpaperManager.cs new file mode 100644 index 00000000..d7217426 --- /dev/null +++ b/Pixeval/Objects/Native/WallpaperManager.cs @@ -0,0 +1,79 @@ +#region Copyright (C) 2019-2020 Dylech30th. All rights reserved. + +// Pixeval - A Strong, Fast and Flexible Pixiv Client +// Copyright (C) 2019-2020 Dylech30th +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as +// published by the Free Software Foundation, either version 3 of the +// License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see . + +#endregion + +using System; +using System.Runtime.InteropServices; +using System.Threading.Tasks; +using System.Windows.Media.Imaging; +using Microsoft.Win32; +using Pixeval.Objects.Primitive; + +namespace Pixeval.Objects.Native +{ + public class WallpaperManager + { + private const int SetDeskWallpaper = 20; + private const int UpdateIniFile = 0x01; + private const int SendWinIniChange = 0x02; + + public WallpaperManager(string storeLocation, BitmapSource background) + { + StoreLocation = storeLocation; + Background = background; + } + + public string StoreLocation { get; set; } + + public BitmapSource Background { get; set; } + + [DllImport("user32.dll", CharSet = CharSet.Auto)] + private static extern int SystemParametersInfo(int uAction, int uParameter, string lpvParameter, int fuWinIni); + + private async Task CreateBmpFile() + { + await Background.Save(StoreLocation); + } + + private static bool CheckOperatingSystemVersion() + { + var win8Version = new Version(6, 2, 9200, 0); + return Environment.OSVersion.Platform == PlatformID.Win32NT && Environment.OSVersion.Version >= win8Version; + } + + private bool IsImageSupportSpan() + { + return Background.PixelWidth / (double) Background.PixelHeight >= 1.7; + } + + public async ValueTask Execute() + { + await CreateBmpFile(); + using var regKey = Registry.CurrentUser.OpenSubKey(@"Control Panel\Desktop", true); + if (regKey == null) + { + return false; + } + regKey.SetValue("WallpaperStyle", (CheckOperatingSystemVersion() && IsImageSupportSpan() ? /* span */ 22 : /* normal */ 10).ToString()); + regKey.SetValue("TitleWallpaper", 0.ToString()); + SystemParametersInfo(SetDeskWallpaper, 0, StoreLocation, UpdateIniFile | SendWinIniChange); + return true; + } + } +} \ No newline at end of file diff --git a/Pixeval/Objects/Primitive/Attributes.cs b/Pixeval/Objects/Primitive/Attributes.cs new file mode 100644 index 00000000..b6006fb9 --- /dev/null +++ b/Pixeval/Objects/Primitive/Attributes.cs @@ -0,0 +1,46 @@ +#region Copyright (C) 2019-2020 Dylech30th. All rights reserved. + +// Pixeval - A Strong, Fast and Flexible Pixiv Client +// Copyright (C) 2019-2020 Dylech30th +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as +// published by the Free Software Foundation, either version 3 of the +// License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see . + +#endregion + +using System; + +namespace Pixeval.Objects.Primitive +{ + [AttributeUsage(AttributeTargets.Field)] + public class EnumAlias : Attribute + { + public EnumAlias(string aliasAs) + { + AliasAs = aliasAs; + } + + public string AliasAs { get; set; } + } + + [AttributeUsage(AttributeTargets.Field)] + public class EnumLocalizedName : Attribute + { + public EnumLocalizedName(string name) + { + Name = name; + } + + public string Name { get; set; } + } +} \ No newline at end of file diff --git a/Pixeval/Objects/Primitive/InternalIO.cs b/Pixeval/Objects/Primitive/InternalIO.cs new file mode 100644 index 00000000..07b583ac --- /dev/null +++ b/Pixeval/Objects/Primitive/InternalIO.cs @@ -0,0 +1,140 @@ +#region Copyright (C) 2019-2020 Dylech30th. All rights reserved. + +// Pixeval - A Strong, Fast and Flexible Pixiv Client +// Copyright (C) 2019-2020 Dylech30th +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as +// published by the Free Software Foundation, either version 3 of the +// License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see . + +#endregion + +using System; +using System.Collections.Generic; +using System.IO; +using System.IO.Compression; +using System.Threading.Tasks; +using System.Windows.Media.Imaging; +using ImageMagick; + +namespace Pixeval.Objects.Primitive +{ + // ReSharper disable once InconsistentNaming + public static class InternalIO + { + public static async Task ToBytes(this Stream stream) + { + if (stream is MemoryStream ms) + { + return ms.ToArray(); + } + + var mStream = new MemoryStream(); + await stream.CopyToAsync(mStream); + return mStream.ToArray(); + } + + public static BitmapImage CreateBitmapImageFromStream(Stream stream) + { + stream.Seek(0, SeekOrigin.Begin); + if (stream.Length == 0) + { + return null; + } + var bmp = new BitmapImage { CreateOptions = BitmapCreateOptions.DelayCreation }; + bmp.BeginInit(); + bmp.CacheOption = BitmapCacheOption.OnLoad; + bmp.StreamSource = stream; + bmp.EndInit(); + bmp.Freeze(); + return bmp; + } + + public static async Task ToByteArrayAsync(this BitmapImage bitmapImage) + { + if (bitmapImage.StreamSource is { } stream) + { + var ms = new MemoryStream(); + await stream.CopyToAsync(ms); + ms.Position = 0L; + return ms.ToArray(); + } + + throw new ArgumentException(nameof(bitmapImage.StreamSource)); + } + + public static IReadOnlyList ReadGifZipEntries(Stream stream) + { + var dis = new List(); + using var zipArchive = new ZipArchive(stream, ZipArchiveMode.Read); + + foreach (var zipArchiveEntry in zipArchive.Entries) + { + using var aStream = zipArchiveEntry.Open(); + + var ms = new MemoryStream(); + aStream.CopyTo(ms); + ms.Position = 0L; + dis.Add(ms); + } + + return dis; + } + + public static IEnumerable ReadGifZipEntries(byte[] bArr) + { + return ReadGifZipEntries(new MemoryStream(bArr)); + } + + public static Stream MergeGifStream(IReadOnlyList streams, IReadOnlyList delay) + { + var ms = new MemoryStream(); + using var mCollection = new MagickImageCollection(); + + for (var i = 0; i < streams.Count; i++) + { + var iStream = streams[i]; + + var img = new MagickImage(iStream) { AnimationDelay = (int) delay[i] }; + mCollection.Add(img); + } + + var settings = new QuantizeSettings { Colors = 256 }; + mCollection.Quantize(settings); + mCollection.Optimize(); + mCollection.Write(ms, MagickFormat.Gif); + return ms; + } + + public static Stream ToStream(this BitmapImage bitmapImage) + { + var ms = new MemoryStream(); + var encoder = new PngBitmapEncoder(); + encoder.Frames.Add(BitmapFrame.Create(bitmapImage)); + encoder.Save(ms); + return ms; + } + + public static async Task Save(this BitmapSource bitmapImage, string location) where TEncoder : BitmapEncoder, new() + { + var encoder = new TEncoder(); + encoder.Frames.Add(BitmapFrame.Create(bitmapImage)); + await using var fs = CreateNewChannel(location); + encoder.Save(fs); + } + + public static FileStream CreateNewChannel(string location) + { + return new FileStream(location, FileMode.Create, FileAccess.ReadWrite, FileShare.None); + } + } +} \ No newline at end of file diff --git a/Pixeval/Objects/Primitive/Reflection.cs b/Pixeval/Objects/Primitive/Reflection.cs new file mode 100644 index 00000000..51a2cd9e --- /dev/null +++ b/Pixeval/Objects/Primitive/Reflection.cs @@ -0,0 +1,44 @@ +#region Copyright (C) 2019-2020 Dylech30th. All rights reserved. + +// Pixeval - A Strong, Fast and Flexible Pixiv Client +// Copyright (C) 2019-2020 Dylech30th +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as +// published by the Free Software Foundation, either version 3 of the +// License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see . + +#endregion + +using System; +using System.Reflection; +using Pixeval.Objects.Exceptions; + +namespace Pixeval.Objects.Primitive +{ + public static class Reflection + { + public static T GetCustomAttribute(this object obj) where T : Attribute + { + return CustomAttributeExtensions.GetCustomAttribute(obj.GetType()) ?? throw new AttributeNotFoundException(typeof(T).ToString()); + } + + public static T GetEnumAttribute(this Enum value) where T : Attribute + { + return value.GetType().GetField(value.ToString())!.GetCustomAttribute(false) ?? throw new AttributeNotFoundException(typeof(T).ToString()); + } + + public static bool AttributeAttached(this Enum value) where T : Attribute + { + return value.GetType().GetField(value.ToString())!.GetCustomAttribute(false) != null; + } + } +} \ No newline at end of file diff --git a/Pixeval/Objects/Primitive/Strings.cs b/Pixeval/Objects/Primitive/Strings.cs new file mode 100644 index 00000000..d8ddc3d9 --- /dev/null +++ b/Pixeval/Objects/Primitive/Strings.cs @@ -0,0 +1,103 @@ +#region Copyright (C) 2019-2020 Dylech30th. All rights reserved. + +// Pixeval - A Strong, Fast and Flexible Pixiv Client +// Copyright (C) 2019-2020 Dylech30th +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as +// published by the Free Software Foundation, either version 3 of the +// License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see . + +#endregion + +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Security.Cryptography; +using System.Text; +using Newtonsoft.Json; + +namespace Pixeval.Objects.Primitive +{ + public static class Strings + { + public static byte[] GetBytes(this string str, Encoding encoding = null) + { + return encoding == null ? Encoding.UTF8.GetBytes(str) : encoding.GetBytes(str); + } + + public static string GetString(this byte[] data, Encoding encoding = null) + { + return encoding == null ? Encoding.UTF8.GetString(data) : encoding.GetString(data); + } + + public static string ToJson(this T obj) + { + return JsonConvert.SerializeObject(obj, new JsonSerializerSettings { Formatting = Formatting.Indented }); + } + + public static T FromJson(this string src) + { + return JsonConvert.DeserializeObject(src); + } + + public static string Join(this IEnumerable enumerable, Func transformer, char delimiter) + { + return string.Join(delimiter, enumerable.Select(transformer)); + } + + public static string Join(this IEnumerable enumerable, char delimiter) + { + return string.Join(delimiter, enumerable); + } + + public static bool IsNullOrEmpty(this string src) + { + return string.IsNullOrEmpty(src); + } + + public static bool EqualsIgnoreCase(this string str1, string str2) + { + return string.Equals(str1, str2, StringComparison.OrdinalIgnoreCase); + } + + public static bool IsNumber(this string str) + { + return int.TryParse(str, out _); + } + + // see https://stackoverflow.com/questions/146134/how-to-remove-illegal-characters-from-path-and-filenames + public static string FormatPath(string original) + { + return string.Concat(original.Split(Path.GetInvalidFileNameChars())); + } + + public static string AssumeImageContentType(string fileName) + { + return fileName[^3..] switch + { + "png" => "image/png", + "jpg" => "image/jpeg", + "jpeg" => "image/jpeg", + "gif" => "image/gif", + _ => "image/jpeg" + }; + } + + public static string Hash(this string str) where T : HashAlgorithm, new() + { + using var crypt = new T(); + var hashBytes = crypt.ComputeHash(str.GetBytes()); + return hashBytes.Select(b => b.ToString("x2")).Aggregate((s1, s2) => s1 + s2); + } + } +} \ No newline at end of file diff --git a/Pixeval/Objects/Primitive/UIHelper.cs b/Pixeval/Objects/Primitive/UIHelper.cs new file mode 100644 index 00000000..76850c63 --- /dev/null +++ b/Pixeval/Objects/Primitive/UIHelper.cs @@ -0,0 +1,260 @@ +#region Copyright (C) 2019-2020 Dylech30th. All rights reserved. + +// Pixeval - A Strong, Fast and Flexible Pixiv Client +// Copyright (C) 2019-2020 Dylech30th +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as +// published by the Free Software Foundation, either version 3 of the +// License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see . + +#endregion + +using System; +using System.Collections; +using System.Collections.Generic; +using System.Collections.ObjectModel; +using System.Reflection; +using System.Runtime.InteropServices; +using System.Windows; +using System.Windows.Controls; +using System.Windows.Controls.Primitives; +using System.Windows.Input; +using System.Windows.Interop; +using System.Windows.Media; +using System.Windows.Media.Animation; +using Microsoft.CSharp.RuntimeBinder; + +namespace Pixeval.Objects.Primitive +{ + public static class UiHelper + { + [DllImport("user32.dll")] + private static extern IntPtr GetForegroundWindow(); + + [DllImport("user32.dll")] + private static extern uint GetWindowThreadProcessId(IntPtr hWnd, IntPtr processId); + + [DllImport("user32.dll")] + private static extern bool AttachThreadInput(uint idAttach, uint idAttachTo, bool fAttach); + + [DllImport("user32.dll")] + public static extern bool SetWindowPos(IntPtr hWnd, IntPtr hWndInsertAfter, int x, int y, int cx, int cy, uint uFlags); + + /// + /// Little trick, from https://stackoverflow.com/questions/257587/bring-a-window-to-the-front-in-wpf + /// + /// window to active + public static void GlobalActivate(this Window w) + { + const uint NoSize = 0x0001; + const uint NoMove = 0x0002; + const uint ShowWindow = 0x0040; + var interopHelper = new WindowInteropHelper(w); + var thisWindowThreadId = GetWindowThreadProcessId(interopHelper.Handle, IntPtr.Zero); + var currentForegroundWindow = GetForegroundWindow(); + var currentForegroundWindowThreadId = GetWindowThreadProcessId(currentForegroundWindow, IntPtr.Zero); + AttachThreadInput(currentForegroundWindowThreadId, thisWindowThreadId, true); + SetWindowPos(interopHelper.Handle, new IntPtr(0), 0, 0, 0, 0, NoSize | NoMove | ShowWindow); + AttachThreadInput(currentForegroundWindowThreadId, thisWindowThreadId, false); + if (w.WindowState == WindowState.Minimized) + { + w.WindowState = WindowState.Normal; + } + w.Show(); + w.Activate(); + } + + public static void Disable(this FrameworkElement element) + { + element.IsEnabled = false; + } + + public static void Enable(this FrameworkElement element) + { + element.IsEnabled = true; + } + + public static IEnumerable FindVisualChildren(DependencyObject depObj) where T : DependencyObject + { + if (depObj != null) + { + for (var i = 0; i < VisualTreeHelper.GetChildrenCount(depObj); i++) + { + var child = VisualTreeHelper.GetChild(depObj, i); + if (child is T dependencyObject) + { + yield return dependencyObject; + } + + foreach (var childOfChild in FindVisualChildren(child)) + { + yield return childOfChild; + } + } + } + } + + public static T DataContext(this FrameworkElement element) + { + return (T) element.DataContext; + } + + public static void SetImageSource(object img, ImageSource imgSource) + { + Application.Current.Dispatcher.Invoke(() => ((Image) img).Source = imgSource); + } + + public static void ReleaseImage(object img) + { + Application.Current.Dispatcher.Invoke(() => ((Image) img).Source = null); + } + + public static ObservableCollection NewItemsSource(ItemsControl itemsControl) + { + var collection = new ObservableCollection(); + SetItemsSource(itemsControl, collection); + return collection; + } + + public static void SetItemsSource(ItemsControl itemsControl, IEnumerable itemSource) + { + itemsControl.ItemsSource = itemSource; + } + + public static void ReleaseItemsSource(ItemsControl listView) + { + listView.ItemsSource = null; + } + + public static void StartDoubleAnimationUseCubicEase(object sender, string path, double from, double to, int milliseconds) + { + var sb = new Storyboard(); + var doubleAnimation = new DoubleAnimation(from, to, TimeSpan.FromMilliseconds(milliseconds)) { EasingFunction = new CubicEase { EasingMode = EasingMode.EaseOut } }; + Storyboard.SetTarget(doubleAnimation, (DependencyObject) sender); + Storyboard.SetTargetProperty(doubleAnimation, new PropertyPath(path)); + sb.Children.Add(doubleAnimation); + sb.Begin(); + } + + public static T GetDataContext(this object sender) + { + if (sender is FrameworkElement element) + { + return element.DataContext(); + } + + throw new NotSupportedException($"parameter must be derive class of {nameof(FrameworkElement)}"); + } + + public static T GetResources(this FrameworkElement element, string name) + { + return (T) element.Resources[name]; + } + + public static void CloseControls(params UIElement[] controls) + { + foreach (var control in controls) + { + CloseControl(control); + } + } + + public static void CloseControl(this UIElement control) + { + try + { + control.Dispatcher.Invoke(() => ((dynamic) control).IsOpen = false); + } + catch (RuntimeBinderException) + { + // ignore + } + } + + public static void OpenControl(this UIElement control) + { + try + { + control.Dispatcher.Invoke(() => ((dynamic) control).IsOpen = true); + } + catch (RuntimeBinderException) + { + // ignore + } + } + + public static void Scroll(ScrollViewer sender, MouseWheelEventArgs e) + { + if (e.Delta > 0) + { + sender.LineUp(); + sender.LineUp(); + } + else + { + sender.LineDown(); + sender.LineDown(); + } + } + } + + public class PopupHelper + { + public static readonly DependencyProperty PopupPlacementTargetProperty = DependencyProperty.RegisterAttached("PopupPlacementTarget", typeof(DependencyObject), typeof(PopupHelper), new PropertyMetadata(null, OnPopupPlacementTargetChanged)); + + public static DependencyObject GetPopupPlacementTarget(DependencyObject obj) + { + return (DependencyObject) obj.GetValue(PopupPlacementTargetProperty); + } + + public static void SetPopupPlacementTarget(DependencyObject obj, DependencyObject value) + { + obj.SetValue(PopupPlacementTargetProperty, value); + } + + private static void OnPopupPlacementTargetChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) + { + if (e.NewValue != null) + { + var popupPopupPlacementTarget = e.NewValue as DependencyObject; + var pop = d as Popup; + + var w = Window.GetWindow(popupPopupPlacementTarget ?? throw new InvalidOperationException()); + if (null != w) + { + w.LocationChanged += (sender, args) => + { + if (pop != null) + { + var offset = pop.HorizontalOffset; + pop.HorizontalOffset = offset + 1; + pop.HorizontalOffset = offset; + } + }; + + w.SizeChanged += (sender, args) => + { + var mi = typeof(Popup).GetMethod("UpdatePosition", BindingFlags.NonPublic | BindingFlags.Instance); + try + { + mi?.Invoke(pop, null); + } + catch + { + // ignored + } + }; + } + } + } + } +} \ No newline at end of file diff --git a/Pixeval/Objects/ValueConverters/BoolToCachingPolicyConverter.cs b/Pixeval/Objects/ValueConverters/BoolToCachingPolicyConverter.cs new file mode 100644 index 00000000..e0603285 --- /dev/null +++ b/Pixeval/Objects/ValueConverters/BoolToCachingPolicyConverter.cs @@ -0,0 +1,40 @@ +#region Copyright (C) 2019-2020 Dylech30th. All rights reserved. + +// Pixeval - A Strong, Fast and Flexible Pixiv Client +// Copyright (C) 2019-2020 Dylech30th +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as +// published by the Free Software Foundation, either version 3 of the +// License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see . + +#endregion + +using System; +using System.Globalization; +using System.Windows.Data; +using Pixeval.Objects.Caching; + +namespace Pixeval.Objects.ValueConverters +{ + public class BoolToCachingPolicyConverter : IValueConverter + { + public object Convert(object value, Type targetType, object parameter, CultureInfo culture) + { + return value is CachingPolicy.File; + } + + public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture) + { + return value is true ? CachingPolicy.File : CachingPolicy.Memory; + } + } +} \ No newline at end of file diff --git a/Pixeval/Objects/ValueConverters/DateTimeConverter.cs b/Pixeval/Objects/ValueConverters/DateTimeConverter.cs new file mode 100644 index 00000000..fea749c0 --- /dev/null +++ b/Pixeval/Objects/ValueConverters/DateTimeConverter.cs @@ -0,0 +1,44 @@ +#region Copyright (C) 2019-2020 Dylech30th. All rights reserved. + +// Pixeval - A Strong, Fast and Flexible Pixiv Client +// Copyright (C) 2019-2020 Dylech30th +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as +// published by the Free Software Foundation, either version 3 of the +// License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see . + +#endregion + +using System; +using System.Globalization; +using System.Windows.Data; + +namespace Pixeval.Objects.ValueConverters +{ + public class DateTimeConverter : IValueConverter + { + public object Convert(object value, Type targetType, object parameter, CultureInfo culture) + { + if (value is DateTime d) + { + return d.ToString("yyyy/MM/dd HH:mm:ss"); + } + + return string.Empty; + } + + public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture) + { + throw new NotImplementedException(); + } + } +} \ No newline at end of file diff --git a/Pixeval/Objects/ValueConverters/DateTimeOffsetConverter.cs b/Pixeval/Objects/ValueConverters/DateTimeOffsetConverter.cs new file mode 100644 index 00000000..1020814c --- /dev/null +++ b/Pixeval/Objects/ValueConverters/DateTimeOffsetConverter.cs @@ -0,0 +1,44 @@ +#region Copyright (C) 2019-2020 Dylech30th. All rights reserved. + +// Pixeval - A Strong, Fast and Flexible Pixiv Client +// Copyright (C) 2019-2020 Dylech30th +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as +// published by the Free Software Foundation, either version 3 of the +// License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see . + +#endregion + +using System; +using System.Globalization; +using System.Windows.Data; + +namespace Pixeval.Objects.ValueConverters +{ + public class DateTimeOffsetConverter : IValueConverter + { + public object Convert(object value, Type targetType, object parameter, CultureInfo culture) + { + if (value is DateTimeOffset d) + { + return d.ToString("yyyy/MM/dd HH:mm:ss"); + } + + return string.Empty; + } + + public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture) + { + throw new NotImplementedException(); + } + } +} \ No newline at end of file diff --git a/Pixeval/Objects/ValueConverters/DoubleToPercentConverter.cs b/Pixeval/Objects/ValueConverters/DoubleToPercentConverter.cs new file mode 100644 index 00000000..37e7d678 --- /dev/null +++ b/Pixeval/Objects/ValueConverters/DoubleToPercentConverter.cs @@ -0,0 +1,44 @@ +#region Copyright (C) 2019-2020 Dylech30th. All rights reserved. + +// Pixeval - A Strong, Fast and Flexible Pixiv Client +// Copyright (C) 2019-2020 Dylech30th +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as +// published by the Free Software Foundation, either version 3 of the +// License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see . + +#endregion + +using System; +using System.Globalization; +using System.Windows.Data; + +namespace Pixeval.Objects.ValueConverters +{ + public class DoubleToPercentConverter : IValueConverter + { + public object Convert(object value, Type targetType, object parameter, CultureInfo culture) + { + if (value is double d) + { + return $"{d * 100:F}%"; + } + + return string.Empty; + } + + public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture) + { + throw new NotImplementedException(); + } + } +} \ No newline at end of file diff --git a/Pixeval/Objects/ValueConverters/EnumToStringConverter.cs b/Pixeval/Objects/ValueConverters/EnumToStringConverter.cs new file mode 100644 index 00000000..1fe1e217 --- /dev/null +++ b/Pixeval/Objects/ValueConverters/EnumToStringConverter.cs @@ -0,0 +1,51 @@ +#region Copyright (C) 2019-2020 Dylech30th. All rights reserved. + +// Pixeval - A Strong, Fast and Flexible Pixiv Client +// Copyright (C) 2019-2020 Dylech30th +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as +// published by the Free Software Foundation, either version 3 of the +// License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see . + +#endregion + +using System; +using System.Globalization; +using System.Windows.Data; + +namespace Pixeval.Objects.ValueConverters +{ + public class EnumToStringConverter : IValueConverter + { + public object Convert(object value, Type targetType, object parameter, CultureInfo culture) + { + if (value is Enum v) + { + try + { + return Enum.GetName(v.GetType(), value); + } + catch + { + return string.Empty; + } + } + + return string.Empty; + } + + public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture) + { + throw new NotImplementedException(); + } + } +} \ No newline at end of file diff --git a/Pixeval/Objects/ValueConverters/IllustSubscriptConverter.cs b/Pixeval/Objects/ValueConverters/IllustSubscriptConverter.cs new file mode 100644 index 00000000..9c1a4354 --- /dev/null +++ b/Pixeval/Objects/ValueConverters/IllustSubscriptConverter.cs @@ -0,0 +1,52 @@ +#region Copyright (C) 2019-2020 Dylech30th. All rights reserved. + +// Pixeval - A Strong, Fast and Flexible Pixiv Client +// Copyright (C) 2019-2020 Dylech30th +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as +// published by the Free Software Foundation, either version 3 of the +// License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see . + +#endregion + +using System; +using System.Globalization; +using System.Windows; +using System.Windows.Data; +using Pixeval.Data.ViewModel; + +namespace Pixeval.Objects.ValueConverters +{ + public class IllustSubscriptConverter : IValueConverter + { + public object Convert(object value, Type targetType, object parameter, CultureInfo culture) + { + var val = (Illustration) value; + if (val == null) + { + return Visibility.Hidden; + } + + if (val.IsManga || val.IsUgoira) + { + return Visibility.Visible; + } + + return Visibility.Hidden; + } + + public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture) + { + throw new NotImplementedException(); + } + } +} \ No newline at end of file diff --git a/Pixeval/Objects/ValueConverters/IllustrationMatchConditionMaskConverter.cs b/Pixeval/Objects/ValueConverters/IllustrationMatchConditionMaskConverter.cs new file mode 100644 index 00000000..1be9e2bb --- /dev/null +++ b/Pixeval/Objects/ValueConverters/IllustrationMatchConditionMaskConverter.cs @@ -0,0 +1,47 @@ +#region Copyright (C) 2019-2020 Dylech30th. All rights reserved. + +// Pixeval - A Strong, Fast and Flexible Pixiv Client +// Copyright (C) 2019-2020 Dylech30th +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as +// published by the Free Software Foundation, either version 3 of the +// License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see . + +#endregion + +using System; +using System.Globalization; +using System.Windows; +using System.Windows.Data; +using Pixeval.Core; +using Pixeval.Data.ViewModel; + +namespace Pixeval.Objects.ValueConverters +{ + public class IllustrationMatchConditionMaskConverter : IMultiValueConverter + { + public object Convert(object[] values, Type targetType, object parameter, CultureInfo culture) + { + if (values[0] is string v && values[1] is Illustration illustration) + { + return AppContext.DefaultQualifier.Qualified(illustration, IllustrationQualification.Parse(v)) ? Visibility.Visible : Visibility.Hidden; + } + + return Visibility.Hidden; + } + + public object[] ConvertBack(object value, Type[] targetTypes, object parameter, CultureInfo culture) + { + throw new NotImplementedException(); + } + } +} \ No newline at end of file diff --git a/Pixeval/Objects/ValueConverters/InverseBooleanConverter.cs b/Pixeval/Objects/ValueConverters/InverseBooleanConverter.cs new file mode 100644 index 00000000..f273b6a6 --- /dev/null +++ b/Pixeval/Objects/ValueConverters/InverseBooleanConverter.cs @@ -0,0 +1,39 @@ +#region Copyright (C) 2019-2020 Dylech30th. All rights reserved. + +// Pixeval - A Strong, Fast and Flexible Pixiv Client +// Copyright (C) 2019-2020 Dylech30th +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as +// published by the Free Software Foundation, either version 3 of the +// License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see . + +#endregion + +using System; +using System.Globalization; +using System.Windows.Data; + +namespace Pixeval.Objects.ValueConverters +{ + public class InverseBooleanConverter : IValueConverter + { + public object Convert(object value, Type targetType, object parameter, CultureInfo culture) + { + return value is bool b && !b; + } + + public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture) + { + throw new NotImplementedException(); + } + } +} \ No newline at end of file diff --git a/Pixeval/Objects/ValueConverters/MultiCultureConverter.cs b/Pixeval/Objects/ValueConverters/MultiCultureConverter.cs new file mode 100644 index 00000000..410091ad --- /dev/null +++ b/Pixeval/Objects/ValueConverters/MultiCultureConverter.cs @@ -0,0 +1,51 @@ +#region Copyright (C) 2019-2020 Dylech30th. All rights reserved. + +// Pixeval - A Strong, Fast and Flexible Pixiv Client +// Copyright (C) 2019-2020 Dylech30th +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as +// published by the Free Software Foundation, either version 3 of the +// License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see . + +#endregion + +using System; +using System.Globalization; +using System.Linq; +using System.Windows.Data; +using Pixeval.Data.ViewModel; + +namespace Pixeval.Objects.ValueConverters +{ + public class MultiCultureConverter : IValueConverter + { + public object Convert(object value, Type targetType, object parameter, CultureInfo culture) + { + if (value is string s) + { + return AppContext.AvailableCultures.FirstOrDefault(cul => cul.Name == s) ?? I18NOption.ChineseSimplified; + } + + return I18NOption.ChineseSimplified; + } + + public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture) + { + if (value is I18NOption option) + { + return option.Name; + } + + return I18NOption.ChineseSimplified.Name; + } + } +} \ No newline at end of file diff --git a/Pixeval/Objects/ValueConverters/NumericConverter.cs b/Pixeval/Objects/ValueConverters/NumericConverter.cs new file mode 100644 index 00000000..00d96b6b --- /dev/null +++ b/Pixeval/Objects/ValueConverters/NumericConverter.cs @@ -0,0 +1,61 @@ +#region Copyright (C) 2019-2020 Dylech30th. All rights reserved. + +// Pixeval - A Strong, Fast and Flexible Pixiv Client +// Copyright (C) 2019-2020 Dylech30th +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as +// published by the Free Software Foundation, either version 3 of the +// License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see . + +#endregion + +using System; +using System.Globalization; +using System.Windows.Data; + +namespace Pixeval.Objects.ValueConverters +{ + public class DoublePlusConverter : IValueConverter + { + public object Convert(object value, Type targetType, object parameter, CultureInfo culture) + { + if (value == null || parameter == null) + { + return 0; + } + return (double) value + (double) parameter; + } + + public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture) + { + throw new NotImplementedException(); + } + } + + public class DoubleDivisionConverter : IValueConverter + { + public object Convert(object value, Type targetType, object parameter, CultureInfo culture) + { + if (value != null && parameter != null) + { + return (double) value / (double) parameter; + } + + return 0; + } + + public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture) + { + throw new NotImplementedException(); + } + } +} \ No newline at end of file diff --git a/Pixeval/Objects/ValueConverters/QueryR18ToggleButtonIsCheckedConverter.cs b/Pixeval/Objects/ValueConverters/QueryR18ToggleButtonIsCheckedConverter.cs new file mode 100644 index 00000000..592ba1b5 --- /dev/null +++ b/Pixeval/Objects/ValueConverters/QueryR18ToggleButtonIsCheckedConverter.cs @@ -0,0 +1,50 @@ +#region Copyright (C) 2019-2020 Dylech30th. All rights reserved. + +// Pixeval - A Strong, Fast and Flexible Pixiv Client +// Copyright (C) 2019-2020 Dylech30th +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as +// published by the Free Software Foundation, either version 3 of the +// License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see . + +#endregion + +using System; +using System.Collections.Generic; +using System.Globalization; +using System.Linq; +using System.Windows.Data; + +namespace Pixeval.Objects.ValueConverters +{ + public class QueryR18ToggleButtonIsCheckedConverter : IValueConverter + { + public object Convert(object value, Type targetType, object parameter, CultureInfo culture) + { + if (value is IEnumerable val) + { + var enumerable = val as string[] ?? val.ToArray(); + if (enumerable.Contains("R-18") && enumerable.Contains("R-18G")) + { + return true; + } + } + + return false; + } + + public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture) + { + throw new NotImplementedException(); + } + } +} \ No newline at end of file diff --git a/Pixeval/Objects/ValueConverters/StringSplitConverter.cs b/Pixeval/Objects/ValueConverters/StringSplitConverter.cs new file mode 100644 index 00000000..1ec44415 --- /dev/null +++ b/Pixeval/Objects/ValueConverters/StringSplitConverter.cs @@ -0,0 +1,45 @@ +#region Copyright (C) 2019-2020 Dylech30th. All rights reserved. + +// Pixeval - A Strong, Fast and Flexible Pixiv Client +// Copyright (C) 2019-2020 Dylech30th +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as +// published by the Free Software Foundation, either version 3 of the +// License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see . + +#endregion + +using System; +using System.Collections.Generic; +using System.Globalization; +using System.Windows.Data; +using Pixeval.Objects.Primitive; + +namespace Pixeval.Objects.ValueConverters +{ + public class StringSplitConverter : IValueConverter + { + public object Convert(object value, Type targetType, object parameter, CultureInfo culture) + { + return value == null ? string.Empty : string.Join(' ', value as IEnumerable ?? Array.Empty()); + } + + public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture) + { + if (value == null || value.ToString().IsNullOrEmpty()) + { + return new List(); + } + return value.ToString()?.Split(" ") ?? Array.Empty(); + } + } +} \ No newline at end of file diff --git a/Pixeval/Objects/ValueConverters/TagMatchEnumToModelConverter.cs b/Pixeval/Objects/ValueConverters/TagMatchEnumToModelConverter.cs new file mode 100644 index 00000000..1c185d15 --- /dev/null +++ b/Pixeval/Objects/ValueConverters/TagMatchEnumToModelConverter.cs @@ -0,0 +1,56 @@ +#region Copyright (C) 2019-2020 Dylech30th. All rights reserved. + +// Pixeval - A Strong, Fast and Flexible Pixiv Client +// Copyright (C) 2019-2020 Dylech30th +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as +// published by the Free Software Foundation, either version 3 of the +// License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see . + +#endregion + +using System; +using System.Globalization; +using System.Windows.Data; +using Pixeval.Core; +using Pixeval.Data.ViewModel; + +namespace Pixeval.Objects.ValueConverters +{ + public class TagMatchEnumToModelConverter : IValueConverter + { + public object Convert(object value, Type targetType, object parameter, CultureInfo culture) + { + if (value is SearchTagMatchOption option) + { + return option switch + { + SearchTagMatchOption.PartialMatchForTags => SearchTagMatchOptionModel.PartialMatchModel, + SearchTagMatchOption.ExactMatchForTags => SearchTagMatchOptionModel.ExactMatchModel, + SearchTagMatchOption.TitleAndCaption => SearchTagMatchOptionModel.TitleAndCaptionModel, + _ => throw new ArgumentOutOfRangeException() + }; + } + return null; + } + + public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture) + { + if (value is SearchTagMatchOptionModel model) + { + return model.Corresponding; + } + + return null; + } + } +} \ No newline at end of file diff --git a/Pixeval/Objects/ValueConverters/TrendsStatConverter.cs b/Pixeval/Objects/ValueConverters/TrendsStatConverter.cs new file mode 100644 index 00000000..06cfafb8 --- /dev/null +++ b/Pixeval/Objects/ValueConverters/TrendsStatConverter.cs @@ -0,0 +1,53 @@ +#region Copyright (C) 2019-2020 Dylech30th. All rights reserved. + +// Pixeval - A Strong, Fast and Flexible Pixiv Client +// Copyright (C) 2019-2020 Dylech30th +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as +// published by the Free Software Foundation, either version 3 of the +// License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see . + +#endregion + +using System; +using System.Globalization; +using System.Windows.Data; +using Pixeval.Data.ViewModel; +using Pixeval.Objects.I18n; + +namespace Pixeval.Objects.ValueConverters +{ + [ValueConversion(typeof(TrendType), typeof(string))] + public class TrendsStatConverter : IValueConverter + { + public object Convert(object value, Type targetType, object parameter, CultureInfo culture) + { + if (value is TrendType obj) + { + return obj switch + { + TrendType.AddIllust => AkaI18N.TrendsAddIllust, + TrendType.AddBookmark => AkaI18N.TrendsAddBookmark, + TrendType.AddFavorite => AkaI18N.TrendsAddFavorite, + _ => string.Empty + }; + } + + return string.Empty; + } + + public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture) + { + throw new NotImplementedException(); + } + } +} \ No newline at end of file diff --git a/Pixeval/Objects/ValueConverters/VisibilityConverter.cs b/Pixeval/Objects/ValueConverters/VisibilityConverter.cs new file mode 100644 index 00000000..0a4c7d3b --- /dev/null +++ b/Pixeval/Objects/ValueConverters/VisibilityConverter.cs @@ -0,0 +1,99 @@ +#region Copyright (C) 2019-2020 Dylech30th. All rights reserved. + +// Pixeval - A Strong, Fast and Flexible Pixiv Client +// Copyright (C) 2019-2020 Dylech30th +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as +// published by the Free Software Foundation, either version 3 of the +// License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see . + +#endregion + +using System; +using System.Globalization; +using System.Linq; +using System.Windows; +using System.Windows.Data; + +namespace Pixeval.Objects.ValueConverters +{ + public class VisibleIfTrueConverter : IValueConverter + { + public object Convert(object value, Type targetType, object parameter, CultureInfo culture) + { + value ??= false; + var val = (bool) value; + return val ? Visibility.Visible : Visibility.Hidden; + } + + public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture) + { + throw new NotImplementedException(); + } + } + + public class VisibleIfFalseConverter : IValueConverter + { + public object Convert(object value, Type targetType, object parameter, CultureInfo culture) + { + value ??= false; + + var val = (bool) value; + return val ? Visibility.Hidden : Visibility.Visible; + } + + public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture) + { + throw new NotImplementedException(); + } + } + + public class ImageListViewVisibilityConverter : IMultiValueConverter + { + public object Convert(object[] values, Type targetType, object parameter, CultureInfo culture) + { + var val = values.Cast().ToArray(); + + if (!(val[0] || val[1] || val[2] || val[3])) + { + return Visibility.Visible; + } + + return Visibility.Hidden; + } + + public object[] ConvertBack(object value, Type[] targetTypes, object parameter, CultureInfo culture) + { + throw new NotImplementedException(); + } + } + + public class UserPreviewListViewVisibilityConverter : IMultiValueConverter + { + public object Convert(object[] values, Type targetType, object parameter, CultureInfo culture) + { + var val = values.Cast().ToArray(); + + if (!val[0] && !val[1] || val[2]) + { + return Visibility.Visible; + } + + return Visibility.Hidden; + } + + public object[] ConvertBack(object value, Type[] targetTypes, object parameter, CultureInfo culture) + { + throw new NotImplementedException(); + } + } +} \ No newline at end of file diff --git a/Pixeval/Persisting/Authentication.cs b/Pixeval/Persisting/Authentication.cs new file mode 100644 index 00000000..f09b4c1b --- /dev/null +++ b/Pixeval/Persisting/Authentication.cs @@ -0,0 +1,162 @@ +#region Copyright (C) 2019-2020 Dylech30th. All rights reserved. + +// Pixeval - A Strong, Fast and Flexible Pixiv Client +// Copyright (C) 2019-2020 Dylech30th +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as +// published by the Free Software Foundation, either version 3 of the +// License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see . + +#endregion + +using System; +using System.Security.Cryptography; +using System.Threading.Tasks; +using Pixeval.Data.Web; +using Pixeval.Data.Web.Delegation; +using Pixeval.Data.Web.Protocol; +using Pixeval.Data.Web.Request; +using Pixeval.Objects.Exceptions; +using Pixeval.Objects.Generic; +using Pixeval.Objects.I18n; +using Pixeval.Objects.Primitive; +using Refit; + +namespace Pixeval.Persisting +{ + public class Authentication + { + private const string ClientHash = "28c1fdd170a5204386cb1313c7077b34f83e4aaf4aa829ce78c231e05b0bae2c"; + + private static string UtcTimeNow => DateTime.UtcNow.ToString("yyyy-MM-ddTHH:mm:ss+00:00"); + + public static async Task AppApiAuthenticate(string name, string pwd) + { + var time = UtcTimeNow; + var hash = (time + ClientHash).Hash(); + + try + { + var token = await RestService.For(HttpClientFactory.PixivApi(ProtocolBase.OAuthBaseUrl, true).Apply(h => h.Timeout = TimeSpan.FromSeconds(10))).GetTokenByPassword(new PasswordTokenRequest { Name = name, Password = pwd }, time, hash); + Session.Current = Session.Parse(pwd, token); + } + catch (TaskCanceledException) + { + throw new AuthenticateFailedException(AkaI18N.AppApiAuthenticateTimeout); + } + } + + public static async Task AppApiAuthenticate(string refreshToken) + { + try + { + var token = await RestService.For(HttpClientFactory.PixivApi(ProtocolBase.OAuthBaseUrl, true).Apply(h => h.Timeout = TimeSpan.FromSeconds(10))).RefreshToken(new RefreshTokenRequest { RefreshToken = refreshToken }); + Session.Current = Session.Parse(Session.Current.Password, token); + } + catch (TaskCanceledException) + { + throw new AuthenticateFailedException(AkaI18N.AppApiAuthenticateTimeout); + } + } + + /* + [Obsolete("This api is obsolete and subject to change", true)] + public static async Task WebApiAuthenticate(string name, string pwd) + { + // create x.509 certificate object for intercepting https traffic, USE AT YOUR OWN RISK + var certificate = await CertificateManager.GetFakeServerCertificate(); + // create https proxy server for intercepting and forwarding https traffic + using var proxyServer = HttpsProxyServer.Create("127.0.0.1", AppContext.ProxyPort, new PixivApiDnsResolver().Lookup()[0].ToString(), certificate); + // create pac file server for providing the proxy-auto-configuration file, + // which is driven by EmbedIO, this is because CefSharp do not accept file uri + using var pacServer = PacFileServer.Create("127.0.0.1", AppContext.PacPort); + pacServer.Start(); + var chrome = SignIn.Instance.ChromiumWebBrowser; + const string LoginUrl = "https://accounts.pixiv.net/login"; + chrome.Address = LoginUrl; + chrome.FrameLoadEnd += (sender, args) => + { + // when the login page is loaded, we will execute the following js snippet + // which is going to fill and submit the form + if (args.Url == LoginUrl) + // ReSharper disable once AccessToDisposedClosure + chrome.ExecuteScriptAsync($@" + var container_login = document.getElementById('container-login'); + var fields = container_login.getElementsByClassName('input-field'); + var account = fields[0].getElementsByTagName('input')[0]; + var password = fields[1].getElementsByTagName('input')[0]; + account.value = '{name}'; + password.value = '{pwd}'; + document.getElementById('container-login').getElementsByClassName('signup-form__submit')[0].click(); + "); + }; + var cancellationTokenSource = new CancellationTokenSource(); + Task.Run(async () => + { + // ReSharper disable AccessToDisposedClosure + while (!cancellationTokenSource.Token.IsCancellationRequested) + { + await Task.Delay(1000, cancellationTokenSource.Token); + var src = await chrome.Dispatcher.Invoke(async () => + { + if (chrome.WebBrowser != null) return await chrome.WebBrowser.GetSourceAsync(); + return ""; + }); + if (src.Contains("error-msg-list__item")) + { + cancellationTokenSource.Cancel(); + chrome.Dispatcher.Invoke(() => + { + MessageBox.Show(AkaI18N.ThisLoginSessionRequiresRecaptcha); + SignIn.Instance.BrowserDialog.IsOpen = true; + }); + } + } + }, cancellationTokenSource.Token); + // polling to check we have got the correct Cookie, which names PHPSESSID and + // has a form like "numbers_hash" + static bool PixivCookieRecorded(Cookie cookie) + { + return Regex.IsMatch(cookie.Domain, @".*\.pixiv\.net") && cookie.Name == "PHPSESSID" && Regex.IsMatch(cookie.Value, @"\d+_.*"); + } + + // create an asynchronous polling task while the authenticate process is running, + // it will check the Cookie + await chrome.AwaitAsync(async c => + { + var visitor = new TaskCookieVisitor(); + Cef.GetGlobalCookieManager().VisitAllCookies(visitor); + return (await visitor.Task).Any(PixivCookieRecorded); + }, 500, TimeSpan.FromMinutes(3)); + + // check if we have got the Cookie when the time limit is exceeded, return successfully + // if it does, otherwise throw an exception + var completionVisitor = new TaskCookieVisitor(); + Cef.GetGlobalCookieManager().VisitAllCookies(completionVisitor); + + chrome.Dispose(); + cancellationTokenSource.Cancel(); + SignIn.Instance.BrowserDialog.CurrentSession?.Close(); + // finalizing objects and save the cookie to user identity + if ((await completionVisitor.Task).FirstOrDefault(PixivCookieRecorded) is { } cookie) + { + Session.Current ??= new Session(); + Session.Current.PhpSessionId = cookie.Value; + Session.Current.CookieCreation = cookie.Creation.ToLocalTime(); + return; + } + + throw new AuthenticateFailedException(AkaI18N.WebApiAuthenticateTimeout); + } + */ + } +} \ No newline at end of file diff --git a/Pixeval/Persisting/Session.cs b/Pixeval/Persisting/Session.cs new file mode 100644 index 00000000..ae15e34b --- /dev/null +++ b/Pixeval/Persisting/Session.cs @@ -0,0 +1,146 @@ +#region Copyright (C) 2019-2020 Dylech30th. All rights reserved. + +// Pixeval - A Strong, Fast and Flexible Pixiv Client +// Copyright (C) 2019-2020 Dylech30th +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as +// published by the Free Software Foundation, either version 3 of the +// License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see . + +#endregion + +using System; +using System.IO; +using System.Net; +using System.Text; +using System.Threading.Tasks; +using AdysTech.CredentialManager; +using Newtonsoft.Json; +using Pixeval.Data.Web.Response; +using Pixeval.Objects.Primitive; + +namespace Pixeval.Persisting +{ + /// + /// A class which represents current session. etc. + /// + public class Session + { + public static Session Current; + + public string Name { get; set; } + + public DateTime ExpireIn { get; set; } + + public string AccessToken { get; set; } + + public string RefreshToken { get; set; } + + public string AvatarUrl { get; set; } + + public string Id { get; set; } + + [JsonIgnore] + public string MailAddress { get; set; } + + public string Account { get; set; } + + [JsonIgnore] + public string Password { get; set; } + + public DateTime CookieCreation { get; set; } + + public bool IsPremium { get; set; } + + public static Session Parse(string password, TokenResponse token) + { + var response = token.ToResponse; + return new Session + { + Name = response.User.Name, + ExpireIn = DateTime.Now + TimeSpan.FromSeconds(response.ExpiresIn), + AccessToken = response.AccessToken, + RefreshToken = response.RefreshToken, + AvatarUrl = response.User.ProfileImageUrls.Px170X170, + Id = response.User.Id.ToString(), + MailAddress = response.User.MailAddress, + Account = response.User.Account, + Password = password, + CookieCreation = Current?.CookieCreation ?? default, + IsPremium = token.ToResponse.User.IsPremium + }; + } + + public override string ToString() + { + return this.ToJson(); + } + + public async Task Store() + { + await File.WriteAllTextAsync(Path.Combine(AppContext.ConfFolder, AppContext.ConfigurationFileName), ToString()); + CredentialManager.SaveCredentials(AppContext.AppIdentifier, new NetworkCredential(MailAddress, Password)); + } + + public static async Task Restore() + { + Current = (await File.ReadAllTextAsync(Path.Combine(AppContext.ConfFolder, AppContext.ConfigurationFileName), Encoding.UTF8)).FromJson(); + var credential = CredentialManager.GetCredentials(AppContext.AppIdentifier); + Current.MailAddress = credential.UserName; + Current.Password = credential.Password; + } + + public static bool ConfExists() + { + var path = Path.Combine(AppContext.ConfFolder, AppContext.ConfigurationFileName); + return File.Exists(path) && new FileInfo(path).Length != 0 && CredentialManager.GetCredentials(AppContext.AppIdentifier) != null; + } + + public static bool AppApiRefreshRequired(Session identity) + { + return identity == null || identity.AccessToken.IsNullOrEmpty() || identity.ExpireIn == default || identity.ExpireIn <= DateTime.Now; + } + + public static async Task RefreshIfRequired() + { + if (Current == null) + { + await Restore(); + } + + if (AppApiRefreshRequired(Current)) + { + if (Current?.RefreshToken.IsNullOrEmpty() is true) + { + await Authentication.AppApiAuthenticate(Current?.MailAddress, Current?.Password); + } + else + { + await Authentication.AppApiAuthenticate(Current?.RefreshToken); + } + } + } + + public static void Clear() + { + if (File.Exists(Path.Combine(AppContext.ConfFolder, AppContext.ConfigurationFileName))) + { + File.Delete(Path.Combine(AppContext.ConfFolder, AppContext.ConfigurationFileName)); + } + if (CredentialManager.GetCredentials(AppContext.AppIdentifier) != null) + { + CredentialManager.RemoveCredentials(AppContext.AppIdentifier); + } + Current = new Session(); + } + } +} \ No newline at end of file diff --git a/Pixeval/Persisting/Settings.cs b/Pixeval/Persisting/Settings.cs new file mode 100644 index 00000000..4c3e3b5d --- /dev/null +++ b/Pixeval/Persisting/Settings.cs @@ -0,0 +1,126 @@ +#region Copyright (C) 2019-2020 Dylech30th. All rights reserved. + +// Pixeval - A Strong, Fast and Flexible Pixiv Client +// Copyright (C) 2019-2020 Dylech30th +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as +// published by the Free Software Foundation, either version 3 of the +// License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see . + +#endregion + +using System; +using System.Collections.Generic; +using System.IO; +using System.Threading.Tasks; +using Newtonsoft.Json; +using Newtonsoft.Json.Converters; +using Pixeval.Core; +using Pixeval.Objects.Primitive; +using PropertyChanged; + +namespace Pixeval.Persisting +{ + /// + /// A class represents user preference + /// + [AddINotifyPropertyChangedInterface] + public class Settings + { + [JsonIgnore] + public static Settings Global = new Settings(); + + private string downloadLocation = Environment.GetFolderPath(Environment.SpecialFolder.MyPictures); + + public bool SortOnInserting { get; set; } + + public int MinBookmark { get; set; } = 1; + + public bool RecommendIllustrator { get; set; } + + public string DownloadLocation + { + get => downloadLocation.IsNullOrEmpty() ? Environment.GetFolderPath(Environment.SpecialFolder.MyPictures) : downloadLocation; + set => downloadLocation = value; + } + + [JsonConverter(typeof(StringEnumConverter))] + public SearchTagMatchOption TagMatchOption { get; set; } = SearchTagMatchOption.PartialMatchForTags; + + public string Culture { get; set; } = "zh-cn"; + + public int QueryPages { get; set; } = 1; + + public int QueryStart { get; set; } = 1; + + public bool DirectConnect { get; set; } + + public int SpotlightQueryStart { get; set; } = 1; + + public ISet ExcludeTag { get; set; } = new HashSet(); + + public ISet IncludeTag { get; set; } = new HashSet(); + + public bool CreateNewFolderWhenDownloadFromUser { get; set; } + + public string Cookie { get; set; } + + public string MirrorServer { get; set; } + + public override string ToString() + { + return this.ToJson(); + } + + public async Task Store() + { + await File.WriteAllTextAsync(Path.Combine(AppContext.SettingsFolder, "settings.json"), Global.ToString()); + } + + public static async Task Restore() + { + if (File.Exists(Path.Combine(AppContext.SettingsFolder, "settings.json"))) + { + Global = (await File.ReadAllTextAsync(Path.Combine(AppContext.SettingsFolder, "settings.json"))).FromJson(); + } + else + { + Initialize(); + } + } + + public static void Initialize() + { + if (File.Exists(Path.Combine(AppContext.SettingsFolder, "settings.json"))) + { + File.Delete(Path.Combine(AppContext.SettingsFolder, "settings.json")); + } + + Global.downloadLocation = Environment.GetFolderPath(Environment.SpecialFolder.MyPictures); + Global.DownloadLocation = string.Empty; + Global.SortOnInserting = false; + Global.IncludeTag = new HashSet(); + Global.ExcludeTag = new HashSet(); + Global.DirectConnect = false; + Global.MinBookmark = 0; + Global.QueryPages = 1; + Global.QueryStart = 1; + Global.SpotlightQueryStart = 1; + Global.RecommendIllustrator = false; + Global.CreateNewFolderWhenDownloadFromUser = false; + Global.TagMatchOption = SearchTagMatchOption.PartialMatchForTags; + Global.Culture = "zh-cn"; + Global.Cookie = string.Empty; + Global.MirrorServer = string.Empty; + } + } +} \ No newline at end of file diff --git a/Pixeval/Pixeval.csproj b/Pixeval/Pixeval.csproj new file mode 100644 index 00000000..eaec9e18 --- /dev/null +++ b/Pixeval/Pixeval.csproj @@ -0,0 +1,67 @@ + + + + WinExe + netcoreapp3.1 + true + win-x64 + A Strong, Fast and Flexible Pixiv Client + false + Pixeval.App + Resources\pxlogo.ico + false + + x64 + $(NoWarn);NU1701 + + + + Off + app.manifest + + + + + true + + + true + + + true + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Pixeval/Properties/Annotations.cs b/Pixeval/Properties/Annotations.cs new file mode 100644 index 00000000..dea1f4ab --- /dev/null +++ b/Pixeval/Properties/Annotations.cs @@ -0,0 +1,1432 @@ +#region Copyright (C) 2019-2020 Dylech30th. All rights reserved. + +// Pixeval - A Strong, Fast and Flexible Pixiv Client +// Copyright (C) 2019-2020 Dylech30th +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as +// published by the Free Software Foundation, either version 3 of the +// License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see . + +#endregion + +using System; + +// ReSharper disable InheritdocConsiderUsage + +#pragma warning disable 1591 +// ReSharper disable UnusedMember.Global +// ReSharper disable MemberCanBePrivate.Global +// ReSharper disable UnusedAutoPropertyAccessor.Global +// ReSharper disable IntroduceOptionalParameters.Global +// ReSharper disable MemberCanBeProtected.Global +// ReSharper disable InconsistentNaming + +namespace Pixeval.Annotations +{ + /// + /// Indicates that the value of the marked element could be null sometimes, + /// so checking for null is required before its usage. + /// + /// + /// + /// [CanBeNull] object Test() => null; + /// + /// void UseTest() { + /// var p = Test(); + /// var s = p.ToString(); // Warning: Possible 'System.NullReferenceException' + /// } + /// + /// + [AttributeUsage(AttributeTargets.Method | AttributeTargets.Parameter | AttributeTargets.Property | AttributeTargets.Delegate | AttributeTargets.Field | AttributeTargets.Event | AttributeTargets.Class | AttributeTargets.Interface | AttributeTargets.GenericParameter)] + public sealed class CanBeNullAttribute : Attribute + { + } + + /// + /// Indicates that the value of the marked element can never be null. + /// + /// + /// + /// [NotNull] object Foo() { + /// return null; // Warning: Possible 'null' assignment + /// } + /// + /// + [AttributeUsage(AttributeTargets.Method | AttributeTargets.Parameter | AttributeTargets.Property | AttributeTargets.Delegate | AttributeTargets.Field | AttributeTargets.Event | AttributeTargets.Class | AttributeTargets.Interface | AttributeTargets.GenericParameter)] + public sealed class NotNullAttribute : Attribute + { + } + + /// + /// Can be applied to symbols of types derived from IEnumerable as well as to symbols of Task + /// and Lazy classes to indicate that the value of a collection item, of the Task.Result property + /// or of the Lazy.Value property can never be null. + /// + /// + /// + /// public void Foo([ItemNotNull]List<string> books) + /// { + /// foreach (var book in books) { + /// if (book != null) // Warning: Expression is always true + /// Console.WriteLine(book.ToUpper()); + /// } + /// } + /// + /// + [AttributeUsage(AttributeTargets.Method | AttributeTargets.Parameter | AttributeTargets.Property | AttributeTargets.Delegate | AttributeTargets.Field)] + public sealed class ItemNotNullAttribute : Attribute + { + } + + /// + /// Can be applied to symbols of types derived from IEnumerable as well as to symbols of Task + /// and Lazy classes to indicate that the value of a collection item, of the Task.Result property + /// or of the Lazy.Value property can be null. + /// + /// + /// + /// public void Foo([ItemCanBeNull]List<string> books) + /// { + /// foreach (var book in books) + /// { + /// // Warning: Possible 'System.NullReferenceException' + /// Console.WriteLine(book.ToUpper()); + /// } + /// } + /// + /// + [AttributeUsage(AttributeTargets.Method | AttributeTargets.Parameter | AttributeTargets.Property | AttributeTargets.Delegate | AttributeTargets.Field)] + public sealed class ItemCanBeNullAttribute : Attribute + { + } + + /// + /// Indicates that the marked method builds string by the format pattern and (optional) arguments. + /// The parameter, which contains the format string, should be given in constructor. The format string + /// should be in -like form. + /// + /// + /// + /// [StringFormatMethod("message")] + /// void ShowError(string message, params object[] args) { /* do something */ } + /// + /// void Foo() { + /// ShowError("Failed: {0}"); // Warning: Non-existing argument in format string + /// } + /// + /// + [AttributeUsage(AttributeTargets.Constructor | AttributeTargets.Method | AttributeTargets.Property | AttributeTargets.Delegate)] + public sealed class StringFormatMethodAttribute : Attribute + { + /// + /// Specifies which parameter of an annotated method should be treated as the format string + /// + public StringFormatMethodAttribute([NotNull] string formatParameterName) + { + FormatParameterName = formatParameterName; + } + + [NotNull] + public string FormatParameterName { get; } + } + + /// + /// Use this annotation to specify a type that contains static or const fields + /// with values for the annotated property/field/parameter. + /// The specified type will be used to improve completion suggestions. + /// + /// + /// + /// namespace TestNamespace + /// { + /// public class Constants + /// { + /// public static int INT_CONST = 1; + /// public const string STRING_CONST = "1"; + /// } + /// + /// public class Class1 + /// { + /// [ValueProvider("TestNamespace.Constants")] public int myField; + /// public void Foo([ValueProvider("TestNamespace.Constants")] string str) { } + /// + /// public void Test() + /// { + /// Foo(/*try completion here*/);// + /// myField = /*try completion here*/ + /// } + /// } + /// } + /// + /// + [AttributeUsage(AttributeTargets.Parameter | AttributeTargets.Property | AttributeTargets.Field, AllowMultiple = true)] + public sealed class ValueProviderAttribute : Attribute + { + public ValueProviderAttribute([NotNull] string name) + { + Name = name; + } + + [NotNull] + public string Name { get; } + } + + /// + /// Indicates that the integral value falls into the specified interval. + /// It's allowed to specify multiple non-intersecting intervals. + /// Values of interval boundaries are inclusive. + /// + /// + /// + /// void Foo([ValueRange(0, 100)] int value) { + /// if (value == -1) { // Warning: Expression is always 'false' + /// ... + /// } + /// } + /// + /// + [AttributeUsage(AttributeTargets.Parameter | AttributeTargets.Field | AttributeTargets.Property | AttributeTargets.Method | AttributeTargets.Delegate, AllowMultiple = true)] + public sealed class ValueRangeAttribute : Attribute + { + public ValueRangeAttribute(long from, long to) + { + From = from; + To = to; + } + + public ValueRangeAttribute(ulong from, ulong to) + { + From = from; + To = to; + } + + public ValueRangeAttribute(long value) + { + From = To = value; + } + + public ValueRangeAttribute(ulong value) + { + From = To = value; + } + + public object From { get; } + public object To { get; } + } + + /// + /// Indicates that the integral value never falls below zero. + /// + /// + /// + /// void Foo([NonNegativeValue] int value) { + /// if (value == -1) { // Warning: Expression is always 'false' + /// ... + /// } + /// } + /// + /// + [AttributeUsage(AttributeTargets.Parameter | AttributeTargets.Field | AttributeTargets.Property | AttributeTargets.Method | AttributeTargets.Delegate)] + public sealed class NonNegativeValueAttribute : Attribute + { + } + + /// + /// Indicates that the function argument should be a string literal and match one + /// of the parameters of the caller function. For example, ReSharper annotates + /// the parameter of . + /// + /// + /// + /// void Foo(string param) { + /// if (param == null) + /// throw new ArgumentNullException("par"); // Warning: Cannot resolve symbol + /// } + /// + /// + [AttributeUsage(AttributeTargets.Parameter)] + public sealed class InvokerParameterNameAttribute : Attribute + { + } + + /// + /// Indicates that the method is contained in a type that implements + /// System.ComponentModel.INotifyPropertyChanged interface and this method + /// is used to notify that some property value changed. + /// + /// + /// The method should be non-static and conform to one of the supported signatures: + /// + /// + /// NotifyChanged(string) + /// + /// + /// NotifyChanged(params string[]) + /// + /// + /// NotifyChanged{T}(Expression{Func{T}}) + /// + /// + /// NotifyChanged{T,U}(Expression{Func{T,U}}) + /// + /// + /// SetProperty{T}(ref T, T, string) + /// + /// + /// + /// + /// + /// public class Foo : INotifyPropertyChanged { + /// public event PropertyChangedEventHandler PropertyChanged; + /// + /// [NotifyPropertyChangedInvocator] + /// protected virtual void NotifyChanged(string propertyName) { ... } + /// + /// string _name; + /// + /// public string Name { + /// get { return _name; } + /// set { _name = value; NotifyChanged("LastName"); /* Warning */ } + /// } + /// } + /// + /// Examples of generated notifications: + /// + /// + /// NotifyChanged("Property") + /// + /// + /// NotifyChanged(() => Property) + /// + /// + /// NotifyChanged((VM x) => x.Property) + /// + /// + /// SetProperty(ref myField, value, "Property") + /// + /// + /// + [AttributeUsage(AttributeTargets.Method)] + public sealed class NotifyPropertyChangedInvocatorAttribute : Attribute + { + public NotifyPropertyChangedInvocatorAttribute() + { + } + + public NotifyPropertyChangedInvocatorAttribute([NotNull] string parameterName) + { + ParameterName = parameterName; + } + + [CanBeNull] + public string ParameterName { get; } + } + + /// + /// Describes dependency between method input and output. + /// + /// + ///

Function Definition Table syntax:

+ /// + /// FDT ::= FDTRow [;FDTRow]* + /// FDTRow ::= Input => Output | Output <= Input + /// Input ::= ParameterName: Value [, Input]* + /// Output ::= [ParameterName: Value]* {halt|stop|void|nothing|Value} + /// Value ::= true | false | null | notnull | canbenull + /// + /// If the method has a single input parameter, its name could be omitted.
+ /// Using halt (or void/nothing, which is the same) for the method output + /// means that the method doesn't return normally (throws or terminates the process).
+ /// Value canbenull is only applicable for output parameters.
+ /// You can use multiple [ContractAnnotation] for each FDT row, or use single attribute + /// with rows separated by semicolon. There is no notion of order rows, all rows are checked + /// for applicability and applied per each program state tracked by the analysis engine.
+ ///
+ /// + /// + /// + /// + /// [ContractAnnotation("=> halt")] + /// public void TerminationMethod() + /// + /// + /// + /// + /// [ContractAnnotation("null <= param:null")] // reverse condition syntax + /// public string GetName(string surname) + /// + /// + /// + /// + /// [ContractAnnotation("s:null => true")] + /// public bool IsNullOrEmpty(string s) // string.IsNullOrEmpty() + /// + /// + /// + /// + /// // A method that returns null if the parameter is null, + /// // and not null if the parameter is not null + /// [ContractAnnotation("null => null; notnull => notnull")] + /// public object Transform(object data) + /// + /// + /// + /// + /// [ContractAnnotation("=> true, result: notnull; => false, result: null")] + /// public bool TryParse(string s, out Person result) + /// + /// + /// + /// + [AttributeUsage(AttributeTargets.Method, AllowMultiple = true)] + public sealed class ContractAnnotationAttribute : Attribute + { + public ContractAnnotationAttribute([NotNull] string contract) : this(contract, false) + { + } + + public ContractAnnotationAttribute([NotNull] string contract, bool forceFullStates) + { + Contract = contract; + ForceFullStates = forceFullStates; + } + + [NotNull] + public string Contract { get; } + + public bool ForceFullStates { get; } + } + + /// + /// Indicates whether the marked element should be localized. + /// + /// + /// + /// [LocalizationRequiredAttribute(true)] + /// class Foo { + /// string str = "my string"; // Warning: Localizable string + /// } + /// + /// + [AttributeUsage(AttributeTargets.All)] + public sealed class LocalizationRequiredAttribute : Attribute + { + public LocalizationRequiredAttribute() : this(true) + { + } + + public LocalizationRequiredAttribute(bool required) + { + Required = required; + } + + public bool Required { get; } + } + + /// + /// Indicates that the value of the marked type (or its derivatives) + /// cannot be compared using '==' or '!=' operators and Equals() + /// should be used instead. However, using '==' or '!=' for comparison + /// with null is always permitted. + /// + /// + /// + /// [CannotApplyEqualityOperator] + /// class NoEquality { } + /// + /// class UsesNoEquality { + /// void Test() { + /// var ca1 = new NoEquality(); + /// var ca2 = new NoEquality(); + /// if (ca1 != null) { // OK + /// bool condition = ca1 == ca2; // Warning + /// } + /// } + /// } + /// + /// + [AttributeUsage(AttributeTargets.Interface | AttributeTargets.Class | AttributeTargets.Struct)] + public sealed class CannotApplyEqualityOperatorAttribute : Attribute + { + } + + /// + /// When applied to a target attribute, specifies a requirement for any type marked + /// with the target attribute to implement or inherit specific type or types. + /// + /// + /// + /// [BaseTypeRequired(typeof(IComponent)] // Specify requirement + /// class ComponentAttribute : Attribute { } + /// + /// [Component] // ComponentAttribute requires implementing IComponent interface + /// class MyComponent : IComponent { } + /// + /// + [AttributeUsage(AttributeTargets.Class, AllowMultiple = true)] + [BaseTypeRequired(typeof(Attribute))] + public sealed class BaseTypeRequiredAttribute : Attribute + { + public BaseTypeRequiredAttribute([NotNull] Type baseType) + { + BaseType = baseType; + } + + [NotNull] + public Type BaseType { get; } + } + + /// + /// Indicates that the marked symbol is used implicitly (e.g. via reflection, in external library), + /// so this symbol will not be reported as unused (as well as by other usage inspections). + /// + [AttributeUsage(AttributeTargets.All)] + public sealed class UsedImplicitlyAttribute : Attribute + { + public UsedImplicitlyAttribute() : this(ImplicitUseKindFlags.Default, ImplicitUseTargetFlags.Default) + { + } + + public UsedImplicitlyAttribute(ImplicitUseKindFlags useKindFlags) : this(useKindFlags, ImplicitUseTargetFlags.Default) + { + } + + public UsedImplicitlyAttribute(ImplicitUseTargetFlags targetFlags) : this(ImplicitUseKindFlags.Default, targetFlags) + { + } + + public UsedImplicitlyAttribute(ImplicitUseKindFlags useKindFlags, ImplicitUseTargetFlags targetFlags) + { + UseKindFlags = useKindFlags; + TargetFlags = targetFlags; + } + + public ImplicitUseKindFlags UseKindFlags { get; } + + public ImplicitUseTargetFlags TargetFlags { get; } + } + + /// + /// Can be applied to attributes, type parameters, and parameters of a type assignable from + /// . + /// When applied to an attribute, the decorated attribute behaves the same as . + /// When applied to a type parameter or to a parameter of type , indicates that the + /// corresponding type + /// is used implicitly. + /// + [AttributeUsage(AttributeTargets.Class | AttributeTargets.GenericParameter | AttributeTargets.Parameter)] + public sealed class MeansImplicitUseAttribute : Attribute + { + public MeansImplicitUseAttribute() : this(ImplicitUseKindFlags.Default, ImplicitUseTargetFlags.Default) + { + } + + public MeansImplicitUseAttribute(ImplicitUseKindFlags useKindFlags) : this(useKindFlags, ImplicitUseTargetFlags.Default) + { + } + + public MeansImplicitUseAttribute(ImplicitUseTargetFlags targetFlags) : this(ImplicitUseKindFlags.Default, targetFlags) + { + } + + public MeansImplicitUseAttribute(ImplicitUseKindFlags useKindFlags, ImplicitUseTargetFlags targetFlags) + { + UseKindFlags = useKindFlags; + TargetFlags = targetFlags; + } + + [UsedImplicitly] + public ImplicitUseKindFlags UseKindFlags { get; } + + [UsedImplicitly] + public ImplicitUseTargetFlags TargetFlags { get; } + } + + /// + /// Specify the details of implicitly used symbol when it is marked + /// with or . + /// + [Flags] + public enum ImplicitUseKindFlags + { + Default = Access | Assign | InstantiatedWithFixedConstructorSignature, + + /// Only entity marked with attribute considered used. + Access = 1, + + /// Indicates implicit assignment to a member. + Assign = 2, + + /// + /// Indicates implicit instantiation of a type with fixed constructor signature. + /// That means any unused constructor parameters won't be reported as such. + /// + InstantiatedWithFixedConstructorSignature = 4, + + /// Indicates implicit instantiation of a type. + InstantiatedNoFixedConstructorSignature = 8 + } + + /// + /// Specify what is considered to be used implicitly when marked + /// with or . + /// + [Flags] + public enum ImplicitUseTargetFlags + { + Default = Itself, Itself = 1, + + /// Members of entity marked with attribute are considered used. + Members = 2, + + /// Inherited entities are considered used. + WithInheritors = 4, + + /// Entity marked with attribute and all its members considered used. + WithMembers = Itself | Members + } + + /// + /// This attribute is intended to mark publicly available API + /// which should not be removed and so is treated as used. + /// + [MeansImplicitUse(ImplicitUseTargetFlags.WithMembers)] + [AttributeUsage(AttributeTargets.All, Inherited = false)] + public sealed class PublicAPIAttribute : Attribute + { + public PublicAPIAttribute() + { + } + + public PublicAPIAttribute([NotNull] string comment) + { + Comment = comment; + } + + [CanBeNull] + public string Comment { get; } + } + + /// + /// Tells code analysis engine if the parameter is completely handled when the invoked method is on stack. + /// If the parameter is a delegate, indicates that delegate is executed while the method is executed. + /// If the parameter is an enumerable, indicates that it is enumerated while the method is executed. + /// + [AttributeUsage(AttributeTargets.Parameter)] + public sealed class InstantHandleAttribute : Attribute + { + } + + /// + /// Indicates that a method does not make any observable state changes. + /// The same as System.Diagnostics.Contracts.PureAttribute. + /// + /// + /// + /// [Pure] int Multiply(int x, int y) => x * y; + /// + /// void M() { + /// Multiply(123, 42); // Warning: Return value of pure method is not used + /// } + /// + /// + [AttributeUsage(AttributeTargets.Method)] + public sealed class PureAttribute : Attribute + { + } + + /// + /// Indicates that the return value of the method invocation must be used. + /// + /// + /// Methods decorated with this attribute (in contrast to pure methods) might change state, + /// but make no sense without using their return value.
+ /// Similarly to , this attribute + /// will help detecting usages of the method when the return value in not used. + /// Additionally, you can optionally specify a custom message, which will be used when showing warnings, e.g. + /// [MustUseReturnValue("Use the return value to...")]. + ///
+ [AttributeUsage(AttributeTargets.Method)] + public sealed class MustUseReturnValueAttribute : Attribute + { + public MustUseReturnValueAttribute() + { + } + + public MustUseReturnValueAttribute([NotNull] string justification) + { + Justification = justification; + } + + [CanBeNull] + public string Justification { get; } + } + + /// + /// Indicates the type member or parameter of some type, that should be used instead of all other ways + /// to get the value of that type. This annotation is useful when you have some "context" value evaluated + /// and stored somewhere, meaning that all other ways to get this value must be consolidated with existing one. + /// + /// + /// + /// class Foo { + /// [ProvidesContext] IBarService _barService = ...; + /// + /// void ProcessNode(INode node) { + /// DoSomething(node, node.GetGlobalServices().Bar); + /// // ^ Warning: use value of '_barService' field + /// } + /// } + /// + /// + [AttributeUsage(AttributeTargets.Field | AttributeTargets.Property | AttributeTargets.Parameter | AttributeTargets.Method | AttributeTargets.Class | AttributeTargets.Interface | AttributeTargets.Struct | AttributeTargets.GenericParameter)] + public sealed class ProvidesContextAttribute : Attribute + { + } + + /// + /// Indicates that a parameter is a path to a file or a folder within a web project. + /// Path can be relative or absolute, starting from web root (~). + /// + [AttributeUsage(AttributeTargets.Parameter)] + public sealed class PathReferenceAttribute : Attribute + { + public PathReferenceAttribute() + { + } + + public PathReferenceAttribute([NotNull] [PathReference] string basePath) + { + BasePath = basePath; + } + + [CanBeNull] + public string BasePath { get; } + } + + /// + /// An extension method marked with this attribute is processed by code completion + /// as a 'Source Template'. When the extension method is completed over some expression, its source code + /// is automatically expanded like a template at call site. + /// + /// + /// Template method body can contain valid source code and/or special comments starting with '$'. + /// Text inside these comments is added as source code when the template is applied. Template parameters + /// can be used either as additional method parameters or as identifiers wrapped in two '$' signs. + /// Use the attribute to specify macros for parameters. + /// + /// + /// In this example, the 'forEach' method is a source template available over all values + /// of enumerable types, producing ordinary C# 'foreach' statement and placing caret inside block: + /// + /// [SourceTemplate] + /// public static void forEach<T>(this IEnumerable<T> xs) { + /// foreach (var x in xs) { + /// //$ $END$ + /// } + /// } + /// + /// + [AttributeUsage(AttributeTargets.Method)] + public sealed class SourceTemplateAttribute : Attribute + { + } + + /// + /// Allows specifying a macro for a parameter of a source template. + /// + /// + /// You can apply the attribute on the whole method or on any of its additional parameters. The macro expression + /// is defined in the property. When applied on a method, the target + /// template parameter is defined in the property. To apply the macro silently + /// for the parameter, set the property value = -1. + /// + /// + /// Applying the attribute on a source template method: + /// + /// [SourceTemplate, Macro(Target = "item", Expression = "suggestVariableName()")] + /// public static void forEach<T>(this IEnumerable<T> collection) { + /// foreach (var item in collection) { + /// //$ $END$ + /// } + /// } + /// + /// Applying the attribute on a template method parameter: + /// + /// [SourceTemplate] + /// public static void something(this Entity x, [Macro(Expression = "guid()", Editable = -1)] string newguid) { + /// /*$ var $x$Id = "$newguid$" + x.ToString(); + /// x.DoSomething($x$Id); */ + /// } + /// + /// + [AttributeUsage(AttributeTargets.Parameter | AttributeTargets.Method, AllowMultiple = true)] + public sealed class MacroAttribute : Attribute + { + /// + /// Allows specifying a macro that will be executed for a source template + /// parameter when the template is expanded. + /// + [CanBeNull] + public string Expression { get; set; } + + /// + /// Allows specifying which occurrence of the target parameter becomes editable when the template is deployed. + /// + /// + /// If the target parameter is used several times in the template, only one occurrence becomes editable; + /// other occurrences are changed synchronously. To specify the zero-based index of the editable occurrence, + /// use values >= 0. To make the parameter non-editable when the template is expanded, use -1. + /// + public int Editable { get; set; } + + /// + /// Identifies the target parameter of a source template if the + /// is applied on a template method. + /// + [CanBeNull] + public string Target { get; set; } + } + + [AttributeUsage(AttributeTargets.Assembly | AttributeTargets.Field | AttributeTargets.Property, AllowMultiple = true)] + public sealed class AspMvcAreaMasterLocationFormatAttribute : Attribute + { + public AspMvcAreaMasterLocationFormatAttribute([NotNull] string format) + { + Format = format; + } + + [NotNull] + public string Format { get; } + } + + [AttributeUsage(AttributeTargets.Assembly | AttributeTargets.Field | AttributeTargets.Property, AllowMultiple = true)] + public sealed class AspMvcAreaPartialViewLocationFormatAttribute : Attribute + { + public AspMvcAreaPartialViewLocationFormatAttribute([NotNull] string format) + { + Format = format; + } + + [NotNull] + public string Format { get; } + } + + [AttributeUsage(AttributeTargets.Assembly | AttributeTargets.Field | AttributeTargets.Property, AllowMultiple = true)] + public sealed class AspMvcAreaViewLocationFormatAttribute : Attribute + { + public AspMvcAreaViewLocationFormatAttribute([NotNull] string format) + { + Format = format; + } + + [NotNull] + public string Format { get; } + } + + [AttributeUsage(AttributeTargets.Assembly | AttributeTargets.Field | AttributeTargets.Property, AllowMultiple = true)] + public sealed class AspMvcMasterLocationFormatAttribute : Attribute + { + public AspMvcMasterLocationFormatAttribute([NotNull] string format) + { + Format = format; + } + + [NotNull] + public string Format { get; } + } + + [AttributeUsage(AttributeTargets.Assembly | AttributeTargets.Field | AttributeTargets.Property, AllowMultiple = true)] + public sealed class AspMvcPartialViewLocationFormatAttribute : Attribute + { + public AspMvcPartialViewLocationFormatAttribute([NotNull] string format) + { + Format = format; + } + + [NotNull] + public string Format { get; } + } + + [AttributeUsage(AttributeTargets.Assembly | AttributeTargets.Field | AttributeTargets.Property, AllowMultiple = true)] + public sealed class AspMvcViewLocationFormatAttribute : Attribute + { + public AspMvcViewLocationFormatAttribute([NotNull] string format) + { + Format = format; + } + + [NotNull] + public string Format { get; } + } + + /// + /// ASP.NET MVC attribute. If applied to a parameter, indicates that the parameter + /// is an MVC action. If applied to a method, the MVC action name is calculated + /// implicitly from the context. Use this attribute for custom wrappers similar to + /// System.Web.Mvc.Html.ChildActionExtensions.RenderAction(HtmlHelper, String). + /// + [AttributeUsage(AttributeTargets.Parameter | AttributeTargets.Method | AttributeTargets.Field | AttributeTargets.Property)] + public sealed class AspMvcActionAttribute : Attribute + { + public AspMvcActionAttribute() + { + } + + public AspMvcActionAttribute([NotNull] string anonymousProperty) + { + AnonymousProperty = anonymousProperty; + } + + [CanBeNull] + public string AnonymousProperty { get; } + } + + /// + /// ASP.NET MVC attribute. Indicates that the marked parameter is an MVC area. + /// Use this attribute for custom wrappers similar to + /// System.Web.Mvc.Html.ChildActionExtensions.RenderAction(HtmlHelper, String). + /// + [AttributeUsage(AttributeTargets.Parameter | AttributeTargets.Field | AttributeTargets.Property)] + public sealed class AspMvcAreaAttribute : Attribute + { + public AspMvcAreaAttribute() + { + } + + public AspMvcAreaAttribute([NotNull] string anonymousProperty) + { + AnonymousProperty = anonymousProperty; + } + + [CanBeNull] + public string AnonymousProperty { get; } + } + + /// + /// ASP.NET MVC attribute. If applied to a parameter, indicates that the parameter is + /// an MVC controller. If applied to a method, the MVC controller name is calculated + /// implicitly from the context. Use this attribute for custom wrappers similar to + /// System.Web.Mvc.Html.ChildActionExtensions.RenderAction(HtmlHelper, String, String). + /// + [AttributeUsage(AttributeTargets.Parameter | AttributeTargets.Method | AttributeTargets.Field | AttributeTargets.Property)] + public sealed class AspMvcControllerAttribute : Attribute + { + public AspMvcControllerAttribute() + { + } + + public AspMvcControllerAttribute([NotNull] string anonymousProperty) + { + AnonymousProperty = anonymousProperty; + } + + [CanBeNull] + public string AnonymousProperty { get; } + } + + /// + /// ASP.NET MVC attribute. Indicates that the marked parameter is an MVC Master. Use this attribute + /// for custom wrappers similar to System.Web.Mvc.Controller.View(String, String). + /// + [AttributeUsage(AttributeTargets.Parameter | AttributeTargets.Field | AttributeTargets.Property)] + public sealed class AspMvcMasterAttribute : Attribute + { + } + + /// + /// ASP.NET MVC attribute. Indicates that the marked parameter is an MVC model type. Use this attribute + /// for custom wrappers similar to System.Web.Mvc.Controller.View(String, Object). + /// + [AttributeUsage(AttributeTargets.Parameter)] + public sealed class AspMvcModelTypeAttribute : Attribute + { + } + + /// + /// ASP.NET MVC attribute. If applied to a parameter, indicates that the parameter is an MVC + /// partial view. If applied to a method, the MVC partial view name is calculated implicitly + /// from the context. Use this attribute for custom wrappers similar to + /// System.Web.Mvc.Html.RenderPartialExtensions.RenderPartial(HtmlHelper, String). + /// + [AttributeUsage(AttributeTargets.Parameter | AttributeTargets.Method | AttributeTargets.Field | AttributeTargets.Property)] + public sealed class AspMvcPartialViewAttribute : Attribute + { + } + + /// + /// ASP.NET MVC attribute. Allows disabling inspections for MVC views within a class or a method. + /// + [AttributeUsage(AttributeTargets.Class | AttributeTargets.Method)] + public sealed class AspMvcSuppressViewErrorAttribute : Attribute + { + } + + /// + /// ASP.NET MVC attribute. Indicates that a parameter is an MVC display template. + /// Use this attribute for custom wrappers similar to + /// System.Web.Mvc.Html.DisplayExtensions.DisplayForModel(HtmlHelper, String). + /// + [AttributeUsage(AttributeTargets.Parameter | AttributeTargets.Field | AttributeTargets.Property)] + public sealed class AspMvcDisplayTemplateAttribute : Attribute + { + } + + /// + /// ASP.NET MVC attribute. Indicates that the marked parameter is an MVC editor template. + /// Use this attribute for custom wrappers similar to + /// System.Web.Mvc.Html.EditorExtensions.EditorForModel(HtmlHelper, String). + /// + [AttributeUsage(AttributeTargets.Parameter | AttributeTargets.Field | AttributeTargets.Property)] + public sealed class AspMvcEditorTemplateAttribute : Attribute + { + } + + /// + /// ASP.NET MVC attribute. Indicates that the marked parameter is an MVC template. + /// Use this attribute for custom wrappers similar to + /// System.ComponentModel.DataAnnotations.UIHintAttribute(System.String). + /// + [AttributeUsage(AttributeTargets.Parameter | AttributeTargets.Field | AttributeTargets.Property)] + public sealed class AspMvcTemplateAttribute : Attribute + { + } + + /// + /// ASP.NET MVC attribute. If applied to a parameter, indicates that the parameter + /// is an MVC view component. If applied to a method, the MVC view name is calculated implicitly + /// from the context. Use this attribute for custom wrappers similar to + /// System.Web.Mvc.Controller.View(Object). + /// + [AttributeUsage(AttributeTargets.Parameter | AttributeTargets.Method | AttributeTargets.Field | AttributeTargets.Property)] + public sealed class AspMvcViewAttribute : Attribute + { + } + + /// + /// ASP.NET MVC attribute. If applied to a parameter, indicates that the parameter + /// is an MVC view component name. + /// + [AttributeUsage(AttributeTargets.Parameter | AttributeTargets.Field | AttributeTargets.Property)] + public sealed class AspMvcViewComponentAttribute : Attribute + { + } + + /// + /// ASP.NET MVC attribute. If applied to a parameter, indicates that the parameter + /// is an MVC view component view. If applied to a method, the MVC view component view name is default. + /// + [AttributeUsage(AttributeTargets.Parameter | AttributeTargets.Method | AttributeTargets.Field | AttributeTargets.Property)] + public sealed class AspMvcViewComponentViewAttribute : Attribute + { + } + + /// + /// ASP.NET MVC attribute. When applied to a parameter of an attribute, + /// indicates that this parameter is an MVC action name. + /// + /// + /// + /// [ActionName("Foo")] + /// public ActionResult Login(string returnUrl) { + /// ViewBag.ReturnUrl = Url.Action("Foo"); // OK + /// return RedirectToAction("Bar"); // Error: Cannot resolve action + /// } + /// + /// + [AttributeUsage(AttributeTargets.Parameter | AttributeTargets.Property)] + public sealed class AspMvcActionSelectorAttribute : Attribute + { + } + + [AttributeUsage(AttributeTargets.Parameter | AttributeTargets.Property | AttributeTargets.Field)] + public sealed class HtmlElementAttributesAttribute : Attribute + { + public HtmlElementAttributesAttribute() + { + } + + public HtmlElementAttributesAttribute([NotNull] string name) + { + Name = name; + } + + [CanBeNull] + public string Name { get; } + } + + [AttributeUsage(AttributeTargets.Parameter | AttributeTargets.Field | AttributeTargets.Property)] + public sealed class HtmlAttributeValueAttribute : Attribute + { + public HtmlAttributeValueAttribute([NotNull] string name) + { + Name = name; + } + + [NotNull] + public string Name { get; } + } + + /// + /// Razor attribute. Indicates that the marked parameter or method is a Razor section. + /// Use this attribute for custom wrappers similar to + /// System.Web.WebPages.WebPageBase.RenderSection(String). + /// + [AttributeUsage(AttributeTargets.Parameter | AttributeTargets.Method)] + public sealed class RazorSectionAttribute : Attribute + { + } + + /// + /// Indicates how method, constructor invocation, or property access + /// over collection type affects the contents of the collection. + /// Use to specify the access type. + /// + /// + /// Using this attribute only makes sense if all collection methods are marked with this attribute. + /// + /// + /// + /// public class MyStringCollection : List<string> + /// { + /// [CollectionAccess(CollectionAccessType.Read)] + /// public string GetFirstString() + /// { + /// return this.ElementAt(0); + /// } + /// } + /// class Test + /// { + /// public void Foo() + /// { + /// // Warning: Contents of the collection is never updated + /// var col = new MyStringCollection(); + /// string x = col.GetFirstString(); + /// } + /// } + /// + /// + [AttributeUsage(AttributeTargets.Method | AttributeTargets.Constructor | AttributeTargets.Property)] + public sealed class CollectionAccessAttribute : Attribute + { + public CollectionAccessAttribute(CollectionAccessType collectionAccessType) + { + CollectionAccessType = collectionAccessType; + } + + public CollectionAccessType CollectionAccessType { get; } + } + + /// + /// Provides a value for the to define + /// how the collection method invocation affects the contents of the collection. + /// + [Flags] + public enum CollectionAccessType + { + /// Method does not use or modify content of the collection. + None = 0, + + /// Method only reads content of the collection but does not modify it. + Read = 1, + + /// Method can change content of the collection but does not add new elements. + ModifyExistingContent = 2, + + /// Method can add new elements to the collection. + UpdatedContent = ModifyExistingContent | 4 + } + + /// + /// Indicates that the marked method is assertion method, i.e. it halts the control flow if + /// one of the conditions is satisfied. To set the condition, mark one of the parameters with + /// attribute. + /// + [AttributeUsage(AttributeTargets.Method)] + public sealed class AssertionMethodAttribute : Attribute + { + } + + /// + /// Indicates the condition parameter of the assertion method. The method itself should be + /// marked by attribute. The mandatory argument of + /// the attribute is the assertion type. + /// + [AttributeUsage(AttributeTargets.Parameter)] + public sealed class AssertionConditionAttribute : Attribute + { + public AssertionConditionAttribute(AssertionConditionType conditionType) + { + ConditionType = conditionType; + } + + public AssertionConditionType ConditionType { get; } + } + + /// + /// Specifies assertion type. If the assertion method argument satisfies the condition, + /// then the execution continues. Otherwise, execution is assumed to be halted. + /// + public enum AssertionConditionType + { + /// Marked parameter should be evaluated to true. + IS_TRUE = 0, + + /// Marked parameter should be evaluated to false. + IS_FALSE = 1, + + /// Marked parameter should be evaluated to null value. + IS_NULL = 2, + + /// Marked parameter should be evaluated to not null value. + IS_NOT_NULL = 3 + } + + /// + /// Indicates that the marked method unconditionally terminates control flow execution. + /// For example, it could unconditionally throw exception. + /// + [Obsolete("Use [ContractAnnotation('=> halt')] instead")] + [AttributeUsage(AttributeTargets.Method)] + public sealed class TerminatesProgramAttribute : Attribute + { + } + + /// + /// Indicates that method is pure LINQ method, with postponed enumeration (like Enumerable.Select, + /// .Where). This annotation allows inference of [InstantHandle] annotation for parameters + /// of delegate type by analyzing LINQ method chains. + /// + [AttributeUsage(AttributeTargets.Method)] + public sealed class LinqTunnelAttribute : Attribute + { + } + + /// + /// Indicates that IEnumerable passed as a parameter is not enumerated. + /// Use this annotation to suppress the 'Possible multiple enumeration of IEnumerable' inspection. + /// + /// + /// + /// static void ThrowIfNull<T>([NoEnumeration] T v, string n) where T : class + /// { + /// // custom check for null but no enumeration + /// } + /// + /// void Foo(IEnumerable<string> values) + /// { + /// ThrowIfNull(values, nameof(values)); + /// var x = values.ToList(); // No warnings about multiple enumeration + /// } + /// + /// + [AttributeUsage(AttributeTargets.Parameter)] + public sealed class NoEnumerationAttribute : Attribute + { + } + + /// + /// Indicates that the marked parameter is a regular expression pattern. + /// + [AttributeUsage(AttributeTargets.Parameter | AttributeTargets.Field | AttributeTargets.Property)] + public sealed class RegexPatternAttribute : Attribute + { + } + + /// + /// Prevents the Member Reordering feature from tossing members of the marked class. + /// + /// + /// The attribute must be mentioned in your member reordering patterns. + /// + [AttributeUsage(AttributeTargets.Class | AttributeTargets.Interface | AttributeTargets.Struct | AttributeTargets.Enum)] + public sealed class NoReorderAttribute : Attribute + { + } + + /// + /// XAML attribute. Indicates the type that has ItemsSource property and should be treated + /// as ItemsControl-derived type, to enable inner items DataContext type resolve. + /// + [AttributeUsage(AttributeTargets.Class)] + public sealed class XamlItemsControlAttribute : Attribute + { + } + + /// + /// XAML attribute. Indicates the property of some BindingBase-derived type, that + /// is used to bind some item of ItemsControl-derived type. This annotation will + /// enable the DataContext type resolve for XAML bindings for such properties. + /// + /// + /// Property should have the tree ancestor of the ItemsControl type or + /// marked with the attribute. + /// + [AttributeUsage(AttributeTargets.Property)] + public sealed class XamlItemBindingOfItemsControlAttribute : Attribute + { + } + + /// + /// XAML attribute. Indicates the property of some Style-derived type, that + /// is used to style items of ItemsControl-derived type. This annotation will + /// enable the DataContext type resolve for XAML bindings for such properties. + /// + /// + /// Property should have the tree ancestor of the ItemsControl type or + /// marked with the attribute. + /// + [AttributeUsage(AttributeTargets.Property)] + public sealed class XamlItemStyleOfItemsControlAttribute : Attribute + { + } + + [AttributeUsage(AttributeTargets.Class, AllowMultiple = true)] + public sealed class AspChildControlTypeAttribute : Attribute + { + public AspChildControlTypeAttribute([NotNull] string tagName, [NotNull] Type controlType) + { + TagName = tagName; + ControlType = controlType; + } + + [NotNull] + public string TagName { get; } + + [NotNull] + public Type ControlType { get; } + } + + [AttributeUsage(AttributeTargets.Property | AttributeTargets.Method)] + public sealed class AspDataFieldAttribute : Attribute + { + } + + [AttributeUsage(AttributeTargets.Property | AttributeTargets.Method)] + public sealed class AspDataFieldsAttribute : Attribute + { + } + + [AttributeUsage(AttributeTargets.Property)] + public sealed class AspMethodPropertyAttribute : Attribute + { + } + + [AttributeUsage(AttributeTargets.Class, AllowMultiple = true)] + public sealed class AspRequiredAttributeAttribute : Attribute + { + public AspRequiredAttributeAttribute([NotNull] string attribute) + { + Attribute = attribute; + } + + [NotNull] + public string Attribute { get; } + } + + [AttributeUsage(AttributeTargets.Property)] + public sealed class AspTypePropertyAttribute : Attribute + { + public AspTypePropertyAttribute(bool createConstructorReferences) + { + CreateConstructorReferences = createConstructorReferences; + } + + public bool CreateConstructorReferences { get; } + } + + [AttributeUsage(AttributeTargets.Assembly, AllowMultiple = true)] + public sealed class RazorImportNamespaceAttribute : Attribute + { + public RazorImportNamespaceAttribute([NotNull] string name) + { + Name = name; + } + + [NotNull] + public string Name { get; } + } + + [AttributeUsage(AttributeTargets.Assembly, AllowMultiple = true)] + public sealed class RazorInjectionAttribute : Attribute + { + public RazorInjectionAttribute([NotNull] string type, [NotNull] string fieldName) + { + Type = type; + FieldName = fieldName; + } + + [NotNull] + public string Type { get; } + + [NotNull] + public string FieldName { get; } + } + + [AttributeUsage(AttributeTargets.Assembly, AllowMultiple = true)] + public sealed class RazorDirectiveAttribute : Attribute + { + public RazorDirectiveAttribute([NotNull] string directive) + { + Directive = directive; + } + + [NotNull] + public string Directive { get; } + } + + [AttributeUsage(AttributeTargets.Assembly, AllowMultiple = true)] + public sealed class RazorPageBaseTypeAttribute : Attribute + { + public RazorPageBaseTypeAttribute([NotNull] string baseType) + { + BaseType = baseType; + } + + public RazorPageBaseTypeAttribute([NotNull] string baseType, string pageName) + { + BaseType = baseType; + PageName = pageName; + } + + [NotNull] + public string BaseType { get; } + + [CanBeNull] + public string PageName { get; } + } + + [AttributeUsage(AttributeTargets.Method)] + public sealed class RazorHelperCommonAttribute : Attribute + { + } + + [AttributeUsage(AttributeTargets.Property)] + public sealed class RazorLayoutAttribute : Attribute + { + } + + [AttributeUsage(AttributeTargets.Method)] + public sealed class RazorWriteLiteralMethodAttribute : Attribute + { + } + + [AttributeUsage(AttributeTargets.Method)] + public sealed class RazorWriteMethodAttribute : Attribute + { + } + + [AttributeUsage(AttributeTargets.Parameter)] + public sealed class RazorWriteMethodParameterAttribute : Attribute + { + } +} \ No newline at end of file diff --git a/Pixeval/Properties/launchSettings.json b/Pixeval/Properties/launchSettings.json new file mode 100644 index 00000000..c7aadb52 --- /dev/null +++ b/Pixeval/Properties/launchSettings.json @@ -0,0 +1,7 @@ +{ + "profiles": { + "Pixeval": { + "commandName": "Project" + } + } +} \ No newline at end of file diff --git a/Pixeval/Resources/dqueue.ttf b/Pixeval/Resources/dqueue.ttf new file mode 100644 index 0000000000000000000000000000000000000000..5e35b562108b911a3941a348431b2a3afd19ce71 GIT binary patch literal 8028 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 literal 0 HcmV?d00001 diff --git a/Pixeval/Resources/iconfont.ttf b/Pixeval/Resources/iconfont.ttf new file mode 100644 index 0000000000000000000000000000000000000000..2fc4eaff052c55159522dcf5b90a36dc20b9b3ee GIT binary patch literal 16276 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 R!?kFXs+pXYU#jHD`xn;*Gc5oB literal 0 HcmV?d00001 diff --git a/Pixeval/Resources/logo-only.svg b/Pixeval/Resources/logo-only.svg new file mode 100644 index 00000000..3f51b366 --- /dev/null +++ b/Pixeval/Resources/logo-only.svg @@ -0,0 +1,1216 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Pixeval/Resources/pxlogo.ico b/Pixeval/Resources/pxlogo.ico new file mode 100644 index 0000000000000000000000000000000000000000..11b62b5d0b00d434af03df4a3acb0cc499ebcf8a GIT binary patch literal 209762 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 Hjr{))S + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/Pixeval/Resources/resx_zh-cn.xml b/Pixeval/Resources/resx_zh-cn.xml new file mode 100644 index 00000000..f1eee1f4 --- /dev/null +++ b/Pixeval/Resources/resx_zh-cn.xml @@ -0,0 +1,184 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/Pixeval/Resources/saucenao.png b/Pixeval/Resources/saucenao.png new file mode 100644 index 0000000000000000000000000000000000000000..d5700a7feefafc0a85eb024d48cf6d471334c7bf GIT binary patch literal 5599 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.--> + + + + + + 2 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + -45 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 10 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + premium + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/Pixeval/UI/MainWindow.xaml.cs b/Pixeval/UI/MainWindow.xaml.cs new file mode 100644 index 00000000..fefe15f0 --- /dev/null +++ b/Pixeval/UI/MainWindow.xaml.cs @@ -0,0 +1,1218 @@ +#region Copyright (C) 2019-2020 Dylech30th. All rights reserved. + +// Pixeval - A Strong, Fast and Flexible Pixiv Client +// Copyright (C) 2019-2020 Dylech30th +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as +// published by the Free Software Foundation, either version 3 of the +// License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see . + +#endregion + +using System; +using System.Collections.Generic; +using System.Collections.ObjectModel; +using System.Diagnostics; +using System.IO; +using System.Linq; +using System.Net; +using System.Threading.Tasks; +using System.Windows; +using System.Windows.Controls; +using System.Windows.Documents; +using System.Windows.Input; +using System.Windows.Media; +using System.Windows.Media.Animation; +using System.Windows.Media.Effects; +using System.Windows.Media.Imaging; +using System.Windows.Navigation; +using System.Windows.Threading; +using MaterialDesignThemes.Wpf; +using MaterialDesignThemes.Wpf.Transitions; +using Microsoft.WindowsAPICodePack.Dialogs; +using Pixeval.Core; +using Pixeval.Data.ViewModel; +using Pixeval.Data.Web.Delegation; +using Pixeval.Data.Web.Request; +using Pixeval.Objects.Generic; +using Pixeval.Objects.I18n; +using Pixeval.Objects.Native; +using Pixeval.Objects.Primitive; +using Pixeval.Persisting; +using Pixeval.UI.UserControls; +using Refit; +using Xceed.Wpf.AvalonDock.Controls; +#if RELEASE +using System.Net.Http; +using Pixeval.Objects.Exceptions; +using Pixeval.Objects.Exceptions.Logger; + +#endif + +namespace Pixeval.UI +{ + public partial class MainWindow + { + public static MainWindow Instance; + + public static readonly SnackbarMessageQueue MessageQueue = new SnackbarMessageQueue(TimeSpan.FromSeconds(2)) { IgnoreDuplicate = true }; + + public MainWindow() + { + Instance = this; + InitializeComponent(); + NavigatorList.SelectedItem = MenuTab; + MainWindowSnackBar.MessageQueue = MessageQueue; + + if (Dispatcher != null) + { + Dispatcher.UnhandledException += Dispatcher_UnhandledException; + } + +#pragma warning disable 4014 + AcquireRecommendUser(); +#pragma warning restore 4014 + } + + private static void Dispatcher_UnhandledException(object sender, DispatcherUnhandledExceptionEventArgs e) + { +#if RELEASE + switch (e.Exception) + { + case QueryNotRespondingException _: + MessageQueue.Enqueue(AkaI18N.QueryNotResponding); + break; + case ApiException apiException: + if (apiException.StatusCode == HttpStatusCode.BadRequest) + { + MessageQueue.Enqueue(AkaI18N.QueryNotResponding); + } + break; + case HttpRequestException _: break; + default: + ExceptionDumper.WriteException(e.Exception); + break; + } + + e.Handled = true; +#endif + } + + private void DoQueryButton_OnClick(object sender, RoutedEventArgs e) + { + UiHelper.CloseControls(TrendingTagPopup, AutoCompletionPopup); + + if (KeywordTextBox.Text.IsNullOrEmpty()) + { + MessageQueue.Enqueue(AkaI18N.InputIsEmpty); + return; + } + + var keyword = KeywordTextBox.Text; + if (QuerySingleArtistToggleButton.IsChecked == true) + { + ShowArtist(keyword); + } + else if (QueryArtistToggleButton.IsChecked == true) + { + TryQueryUser(keyword); + } + else if (QuerySingleWorkToggleButton.IsChecked == true) + { + TryQuerySingle(keyword); + } + else + { + QueryWorks(keyword); + } + } + + private async void ShowArtist(string userId) + { + if (!userId.IsNumber()) + { + MessageQueue.Enqueue(AkaI18N.UserIdIllegal); + return; + } + + try + { + await HttpClientFactory.AppApiService().GetUserInformation(new UserInformationRequest { Id = userId }); + } + catch (ApiException e) + { + if (e.StatusCode == HttpStatusCode.NotFound) + { + MessageQueue.Enqueue(AkaI18N.CannotFindUser); + return; + } + } + + OpenUserBrowser(); + SetUserBrowserContext(new User { Id = userId }); + } + + private void TryQueryUser(string keyword) + { + MoveDownHomePage(); + SearchingHistoryManager.EnqueueSearchHistory(keyword); + PixivHelper.Enumerate(new UserPreviewAsyncEnumerable(keyword), UiHelper.NewItemsSource(UserPreviewListView)); + } + + private async void TryQuerySingle(string illustId) + { + if (!int.TryParse(illustId, out _)) + { + MessageQueue.Enqueue(AkaI18N.IdIllegal); + return; + } + + try + { + OpenIllustBrowser(await PixivHelper.IllustrationInfo(illustId)); + } + catch (ApiException exception) + { + if (exception.StatusCode == HttpStatusCode.NotFound || exception.StatusCode == HttpStatusCode.BadRequest) + { + MessageQueue.Enqueue(AkaI18N.IdDoNotExists); + } + else + { + throw; + } + } + } + + private void QueryWorks(string keyword) + { + MoveDownHomePage(); + SearchingHistoryManager.EnqueueSearchHistory(keyword); + PixivHelper.Enumerate(Settings.Global.SortOnInserting ? (AbstractQueryAsyncEnumerable) new PopularityQueryAsyncEnumerable(keyword, Settings.Global.TagMatchOption, Session.Current.IsPremium, Settings.Global.QueryStart) : new PublishDateQueryAsyncEnumerable(keyword, Settings.Global.TagMatchOption, Session.Current.IsPremium, Settings.Global.QueryStart), UiHelper.NewItemsSource(ImageListView), Settings.Global.QueryPages); + } + + private void IllustrationContainer_OnMouseLeftButtonDown(object sender, MouseButtonEventArgs e) + { + OpenIllustBrowser(sender.GetDataContext()); + e.Handled = true; + } + + private void IllustrationContainer_OnPreviewMouseRightButtonDown(object sender, MouseButtonEventArgs e) + { + e.Handled = true; + } + + private async void MainWindow_OnLoaded(object sender, RoutedEventArgs e) + { + await AddUserNameAndAvatar(); + } + + private async Task AddUserNameAndAvatar() + { + if (!Session.Current.AvatarUrl.IsNullOrEmpty() && !Session.Current.Name.IsNullOrEmpty()) + { + UserName.Text = Session.Current.Name; + UserAvatar.Source = await PixivIO.FromUrl(Session.Current.AvatarUrl); + } + } + + private void PixevalSettingDialog_OnDialogClosing(object sender, DialogClosingEventArgs e) + { + SettingsTab.IsSelected = false; + } + + private void DownloadQueueDialogHost_OnDialogClosing(object sender, DialogClosingEventArgs e) + { + DownloadListTab.IsSelected = false; + } + + #region 主窗口 + + private async void UserPreviewPopupContent_OnLoaded(object sender, RoutedEventArgs e) + { + var userInfo = sender.GetDataContext(); + var ctrl = (UserPreviewPopupContent) sender; + var usr = await HttpClientFactory.AppApiService().GetUserInformation(new UserInformationRequest { Id = $"{sender.GetDataContext().Id}" }); + var usrEntity = new User + { + Avatar = usr.UserEntity.ProfileImageUrls.Medium, + Background = usr.UserEntity.ProfileImageUrls.Medium, + Follows = (int) usr.UserProfile.TotalFollowUsers, + Id = usr.UserEntity.Id.ToString(), + Introduction = usr.UserEntity.Comment, + IsFollowed = usr.UserEntity.IsFollowed, + IsPremium = usr.UserProfile.IsPremium, + Name = usr.UserEntity.Name, + Thumbnails = sender.GetDataContext().Thumbnails + }; + ctrl.DataContext = usrEntity; + var result = await Tasks.Of(userInfo.Thumbnails.Take(3)).Mapping(PixivIO.FromUrl).Construct().WhenAll(); + ctrl.SetImages(result[0], result[1], result[2]); + } + + private void RecommendIllustratorContainer_OnMouseLeftButtonDown(object sender, MouseButtonEventArgs e) + { + SetUserBrowserContext(sender.GetDataContext()); + } + + private async void MainWindow_OnPreviewKeyDown(object sender, KeyEventArgs e) + { + var lst = (Collection) (BrowsingUser() ? UserIllustsImageListView : ImageListView)?.ItemsSource; + + var browsing = lst != null && IllustBrowserDialogHost.IsOpen; + var dataContext = IllustBrowserDialogHost.GetDataContext(); + + // ReSharper disable once SwitchStatementMissingSomeEnumCasesNoDefault + switch (e.Key) + { + case Key.Oem5: + var inputBoxControl = BrowsingUser() ? UserBrowserConditionInputBox : ConditionInputBox; + inputBoxControl.Visibility = ConditionInputBox.Visibility == Visibility.Visible ? Visibility.Hidden : Visibility.Visible; + await Task.Delay(100); + if (inputBoxControl.Visibility == Visibility.Visible) + { + inputBoxControl.ConditionTextBox.Focus(); + } + else + { + inputBoxControl.Focus(); + } + return; + case Key.Escape when IllustBrowserDialogHost.IsOpen: + IllustBrowserDialogHost.CurrentSession.Close(); + return; + case Key.Escape when PixevalSettingDialog.IsOpen: + if (PixevalSettingDialog.IsOpen) + { + PixevalSettingDialog.CurrentSession.Close(); + } + break; + case var x when x == Key.PageDown || x == Key.Right || x == Key.Space && browsing && !disableKeyEvent: + var nextIndex = lst!.IndexOf(dataContext) + 1; + if (nextIndex <= lst!.Count - 1) + { + SetIllustBrowserIllustrationDataContext(lst[nextIndex]); + } + break; + case var x when x == Key.PageUp || x == Key.Left && browsing && !disableKeyEvent: + var prevIndex = lst!.IndexOf(dataContext) - 1; + if (prevIndex >= 0) + { + SetIllustBrowserIllustrationDataContext(lst[prevIndex]); + } + break; + } + } + + private void NavigatorScrollViewer_OnPreviewMouseWheel(object sender, MouseWheelEventArgs e) + { + UiHelper.Scroll(NavigatorScrollViewer, e); + } + + private async void ReloadRecommendIllustratorButton_OnMouseLeftButtonDown(object sender, MouseButtonEventArgs e) + { + var tb = (TextBlock) sender; + tb.Disable(); + await AcquireRecommendUser(); + tb.Enable(); + } + + private async void RecommendIllustratorAvatar_OnLoaded(object sender, RoutedEventArgs e) + { + var context = sender.GetDataContext(); + UiHelper.SetImageSource(sender, await PixivIO.FromUrl(context.Avatar)); + } + + private async void KeywordTextBox_OnGotFocus(object sender, RoutedEventArgs e) + { + if (AppContext.TrendingTags.IsNullOrEmpty()) + { + AppContext.TrendingTags.AddRange(await PixivClient.Instance.GetTrendingTags()); + } + TrendingTagPopup.OpenControl(); + } + + private async void KeywordTextBox_OnTextChanged(object sender, TextChangedEventArgs e) + { + if (!KeywordTextBox.Text.IsNullOrEmpty()) + { + TrendingTagPopup.CloseControl(); + } + + if (QueryArtistToggleButton.IsChecked == true || QuerySingleArtistToggleButton.IsChecked == true || QuerySingleWorkToggleButton.IsChecked == true) + { + return; + } + + var word = KeywordTextBox.Text; + + try + { + var result = await HttpClientFactory.AppApiService().GetAutoCompletion(new AutoCompletionRequest { Word = word }); + if (result.Tags.Any()) + { + AutoCompletionPopup.OpenControl(); + AutoCompletionListBox.ItemsSource = result.Tags.Select(p => new AutoCompletion { Tag = p.Name, TranslatedName = p.TranslatedName }); + } + } + catch (ApiException) + { + AutoCompletionPopup.CloseControl(); + } + } + + private void KeywordTextBox_OnPreviewKeyDown(object sender, KeyEventArgs e) + { + var key = e.Key; + if (key == Key.Enter) + { + if (AutoCompletionListBox.SelectedIndex != -1) + { + KeywordTextBox.Text = ((AutoCompletion) AutoCompletionListBox.SelectedItem).Tag; + } + } + + AutoCompletionListBox.SelectedIndex = key switch + { + var x when x == Key.Down || x == Key.S => AutoCompletionListBox.SelectedIndex == -1 ? 0 : AutoCompletionListBox.SelectedIndex + 1, + var x when x == Key.Up || x == Key.A => AutoCompletionListBox.SelectedIndex != -1 && AutoCompletionListBox.SelectedIndex != 0 ? AutoCompletionListBox.SelectedIndex - 1 : AutoCompletionListBox.SelectedIndex, + _ => AutoCompletionListBox.SelectedIndex + }; + } + + private void AutoCompletionElement_OnPreviewMouseLeftButtonDown(object sender, MouseButtonEventArgs e) + { + KeywordTextBox.Text = sender.GetDataContext().Tag; + } + + private void QuerySingleWorkToggleButton_OnChecked(object sender, RoutedEventArgs e) + { + QueryArtistToggleButton.IsChecked = false; + QuerySingleArtistToggleButton.IsChecked = false; + } + + private void QueryArtistToggleButton_OnUnchecked(object sender, RoutedEventArgs e) + { + UiHelper.ReleaseItemsSource(UserPreviewListView); + } + + private void QueryArtistToggleButton_OnChecked(object sender, RoutedEventArgs e) + { + QuerySingleWorkToggleButton.IsChecked = false; + QuerySingleArtistToggleButton.IsChecked = false; + } + + private void QuerySingleArtistToggleButton_OnChecked(object sender, RoutedEventArgs e) + { + QuerySingleWorkToggleButton.IsChecked = false; + QueryArtistToggleButton.IsChecked = false; + } + + private void MainWindow_OnMouseDown(object sender, MouseButtonEventArgs e) + { + DeactivateControl(); + } + + private void MainWindow_OnDeactivated(object sender, EventArgs e) + { + DeactivateControl(); + } + + private void DeactivateControl() + { + ConditionInputBox.Visibility = Visibility.Hidden; + UserBrowserConditionInputBox.Visibility = Visibility.Hidden; + ToLoseFocus.Focus(); + UiHelper.CloseControls(TrendingTagPopup, AutoCompletionPopup); + DownloadListTab.IsSelected = false; + } + + private void Hyperlink_OnRequestNavigate(object sender, RequestNavigateEventArgs e) + { + Process.Start(new ProcessStartInfo("cmd", $"/c start {e.Uri.AbsoluteUri}") { CreateNoWindow = true }); + } + + #endregion + + #region 导航栏 + + private void SettingsTab_OnSelected(object sender, RoutedEventArgs e) + { + PixevalSettingDialog.IsOpen = true; + } + + private void DownloadListTab_OnSelected(object sender, RoutedEventArgs e) + { + DownloadQueueDialogHost.IsOpen = true; + } + + private void UpdateIllustTab_OnSelected(object sender, RoutedEventArgs e) + { + MoveDownHomePage(); + MessageQueue.Enqueue(AkaI18N.SearchingUserUpdates); + + PixivHelper.Enumerate(new UserUpdateAsyncEnumerable(), UiHelper.NewItemsSource(ImageListView)); + } + + private void GalleryTab_OnSelected(object sender, RoutedEventArgs e) + { + MoveDownHomePage(); + MessageQueue.Enqueue(AkaI18N.SearchingGallery); + + PixivHelper.Enumerate(AbstractGalleryAsyncEnumerable.Of(Session.Current.Id, PublicRestrictPolicy.IsChecked is true ? RestrictPolicy.Public : RestrictPolicy.Private), UiHelper.NewItemsSource(ImageListView)); + } + + private void RecommendTab_OnSelected(object sender, RoutedEventArgs e) + { + MoveDownHomePage(); + MessageQueue.Enqueue(AkaI18N.SearchingRecommend); + + PixivHelper.Enumerate(Settings.Global.SortOnInserting ? (AbstractRecommendAsyncEnumerable) new PopularityRecommendAsyncEnumerable() : new PlainRecommendAsyncEnumerable(), UiHelper.NewItemsSource(ImageListView), 10); + } + + private void SpotlightTab_OnSelected(object sender, RoutedEventArgs e) + { + MoveDownHomePage(); + + var iterator = new SpotlightQueryAsyncEnumerable(Settings.Global.SpotlightQueryStart); + PixivHelper.Enumerate(iterator, UiHelper.NewItemsSource(SpotlightListView), 10); + } + + private void FollowingTab_OnSelected(object sender, RoutedEventArgs e) + { + MoveDownHomePage(); + MessageQueue.Enqueue(AkaI18N.SearchingFollower); + + PixivHelper.Enumerate(AbstractUserFollowingAsyncEnumerable.Of(Session.Current.Id, PublicRestrictPolicy.IsChecked is true ? RestrictPolicy.Public : RestrictPolicy.Private), UiHelper.NewItemsSource(UserPreviewListView)); + } + + private void FollowingTab_OnUnselected(object sender, RoutedEventArgs e) + { + UiHelper.ReleaseItemsSource(UserPreviewListView); + } + + private void SignOutTab_OnSelected(object sender, RoutedEventArgs e) + { + Session.Clear(); + Settings.Initialize(); + BrowsingHistoryAccessor.GlobalLifeTimeScope.Dispose(); + BrowsingHistoryAccessor.GlobalLifeTimeScope.DropDb(); + var login = new SignIn(); + login.Show(); + Close(); + } + + private void NavigatorList_OnSelectionChanged(object sender, SelectionChangedEventArgs e) + { + TopBarRetract((TranslateTransform) RestrictPolicySelector.RenderTransform); + TopBarRetract((TranslateTransform) RankOptionSelector.RenderTransform); + TrendingTagPopup.CloseControl(); + if (NavigatorList.SelectedItem is ListViewItem current) + { + var translateTransform = (TranslateTransform) HomeDisplayContainer.RenderTransform; + if (current == MenuTab && !translateTransform.Y.Equals(0)) + { + UiHelper.ReleaseItemsSource(SpotlightListView); + UiHelper.ReleaseItemsSource(ImageListView); + UiHelper.ReleaseItemsSource(UserPreviewListView); + EnumeratingSchedule.CancelCurrent(); + MoveUpHomePage(); + } + else if (current != MenuTab && translateTransform.Y.Equals(0)) + { + MoveDownHomePage(); + } + } + } + + private void NavigatorList_OnPreviewKeyDown(object sender, KeyEventArgs e) + { + e.Handled = true; + } + + private void ExternalNavigatorList_OnPreviewKeyDown(object sender, KeyEventArgs e) + { + e.Handled = true; + } + + #endregion + + #region 图片预览 + + private async void Thumbnail_OnLoaded(object sender, RoutedEventArgs e) + { + var dataContext = sender.GetDataContext(); + + if (dataContext != null && Uri.IsWellFormedUriString(dataContext.Thumbnail, UriKind.Absolute)) + { + if (dataContext.Thumbnail != null && Uri.IsWellFormedUriString(dataContext.Thumbnail, UriKind.Absolute)) + { + UiHelper.SetImageSource(sender, await PixivIO.FromUrl(dataContext.Thumbnail)); + } + } + + UiHelper.StartDoubleAnimationUseCubicEase(sender, "(Image.Opacity)", 0, 1, 800); + UiHelper.StartDoubleAnimationUseCubicEase(sender, "(Image.RenderTransform).(ScaleTransform.ScaleX)", 1.2, 1, 800); + UiHelper.StartDoubleAnimationUseCubicEase(sender, "(Image.RenderTransform).(ScaleTransform.ScaleY)", 1.2, 1, 800); + } + + private void Thumbnail_OnUnloaded(object sender, RoutedEventArgs e) + { + UiHelper.ReleaseImage(sender); + } + + private void FavorButton_OnPreviewMouseLeftButtonDown(object sender, RoutedEventArgs e) + { + PixivClient.Instance.PostFavoriteAsync(sender.GetDataContext(), RestrictPolicy.Public); + e.Handled = true; + } + + private void DisfavorButton_OnPreviewMouseLeftButtonDown(object sender, RoutedEventArgs e) + { + PixivClient.Instance.RemoveFavoriteAsync(sender.GetDataContext()); + e.Handled = true; + } + + #endregion + + #region Spotlight + + private async void SpotlightThumbnail_OnLoaded(object sender, RoutedEventArgs e) + { + UiHelper.SetImageSource((Image) sender, await PixivIO.FromUrl(sender.GetDataContext().Thumbnail)); + } + + private async void SpotlightContainer_OnMouseLeftButtonDown(object sender, MouseButtonEventArgs e) + { + MessageQueue.Enqueue(AkaI18N.SearchingSpotlight); + + var article = sender.GetDataContext(); + + var tasks = await Tasks.Of(await PixivClient.Instance.GetArticleWorks(article.Id.ToString())).Mapping(PixivHelper.IllustrationInfo).Construct().WhenAll(); + var result = tasks.Peek(i => + { + i.IsManga = true; + i.FromSpotlight = true; + i.SpotlightTitle = article.Title; + }).ToArray(); + + PixivHelper.RecordTimelineInternal(new BrowsingHistory + { + BrowseObjectId = article.Id.ToString(), + BrowseObjectState = article.Title, + BrowseObjectThumbnail = article.Thumbnail, + IsReferToSpotlight = true, + Type = "spotlight" + }); + + OpenIllustBrowser(result[0].Apply(r => r.MangaMetadata = result.ToArray())); + } + + private void DownloadSpotlightItem_OnClick(object sender, RoutedEventArgs e) + { + sender.GetDataContext().Download(); + MessageQueue.Enqueue(AkaI18N.QueuedDownload); + } + + #endregion + + #region 右键菜单 + + private void DownloadNowMenuItem_OnClick(object sender, RoutedEventArgs e) + { + DownloadOption option = null; + if (BrowsingUser() && IsAtUploadCheckerPosition()) + { + option = new DownloadOption { CreateNewWhenFromUser = Settings.Global.CreateNewFolderWhenDownloadFromUser }; + } + DownloadManager.EnqueueDownloadItem(sender.GetDataContext(), option); + MessageQueue.Enqueue(AkaI18N.QueuedDownload); + } + + private void DownloadToMenuItem_OnClick(object sender, RoutedEventArgs e) + { + using var fileDialog = new CommonOpenFileDialog(AkaI18N.PleaseSelectLocation) { InitialDirectory = Settings.Global.DownloadLocation ?? Environment.GetFolderPath(Environment.SpecialFolder.MyPictures), IsFolderPicker = true }; + + if (fileDialog.ShowDialog() == CommonFileDialogResult.Ok) + { + DownloadManager.EnqueueDownloadItem(sender.GetDataContext(), new DownloadOption { RootDirectory = fileDialog.FileName }); + MessageQueue.Enqueue(AkaI18N.QueuedDownload); + } + } + + private async void DownloadAllNowMenuItem_OnClick(object sender, RoutedEventArgs e) + { + if (await MessageDialog.Warning(WarningDialog, AkaI18N.BatchDownloadAcknowledgment, true) == Objects.DialogResult.Yes) + { + DownloadOption option = null; + if (BrowsingUser() && IsAtUploadCheckerPosition()) + { + option = new DownloadOption { CreateNewWhenFromUser = Settings.Global.CreateNewFolderWhenDownloadFromUser }; + } + + await Task.Run(async () => + { + var source = await Dispatcher.InvokeAsync(GetImageSourceCopy); + foreach (var illustration in source) + { + if (illustration != null) + { + DownloadManager.EnqueueDownloadItem(illustration, option); + } + } + }); + MessageQueue.Enqueue(AkaI18N.QueuedAllToDownload); + } + } + + #endregion + + #region 用户预览 + + private void UserIllustsImageListView_OnPreviewMouseWheel(object sender, MouseWheelEventArgs e) + { + UiHelper.Scroll(UserBrowserPageScrollViewer, e); + } + + private void CloseUserBrowserAnimation_OnCompleted(object sender, EventArgs e) + { + UserBrowserPageScrollViewer.DataContext = null; + UiHelper.ReleaseImage(UserBrowserUserAvatar); + UiHelper.ReleaseItemsSource(UserIllustsImageListView); + } + + private async void PrivateFollow_OnClick(object sender, RoutedEventArgs e) + { + var usr = sender.GetDataContext(); + await PixivClient.Instance.FollowArtist(usr, RestrictPolicy.Private); + } + + private bool animating; + + private void ContentDisplay_OnMouseMove(object sender, MouseEventArgs e) + { + if (!(Navigating(GalleryTab) || Navigating(FollowingTab) || Navigating(RankingTab)) || animating) + { + return; + } + var transform = (TranslateTransform) (Navigating(RankingTab) ? RankOptionSelector.RenderTransform : RestrictPolicySelector.RenderTransform); + if (e.GetPosition(this).Y <= 40 && Width - e.GetPosition(this).X >= 60) + { + TopBarExpand(transform); + } + else if (e.GetPosition(this).Y > 40) + { + TopBarRetract(transform); + } + } + + private void PrivateRestrictPolicy_OnChecked(object sender, RoutedEventArgs e) + { + if (!IsLoaded) + { + return; + } + if (Navigating(GalleryTab)) + { + MessageQueue.Enqueue(AkaI18N.SearchingGallery); + PixivHelper.Enumerate(AbstractGalleryAsyncEnumerable.Of(Session.Current.Id, RestrictPolicy.Private), UiHelper.NewItemsSource(ImageListView)); + } + else if (Navigating(FollowingTab)) + { + MessageQueue.Enqueue(AkaI18N.SearchingFollower); + PixivHelper.Enumerate(AbstractUserFollowingAsyncEnumerable.Of(Session.Current.Id, RestrictPolicy.Private), UiHelper.NewItemsSource(UserPreviewListView)); + } + } + + private void PublicRestrictPolicy_OnChecked(object sender, RoutedEventArgs e) + { + if (!IsLoaded) + { + return; + } + if (Navigating(GalleryTab)) + { + MessageQueue.Enqueue(AkaI18N.SearchingGallery); + PixivHelper.Enumerate(AbstractGalleryAsyncEnumerable.Of(Session.Current.Id, RestrictPolicy.Public), UiHelper.NewItemsSource(ImageListView)); + } + else if (Navigating(FollowingTab)) + { + MessageQueue.Enqueue(AkaI18N.SearchingFollower); + PixivHelper.Enumerate(AbstractUserFollowingAsyncEnumerable.Of(Session.Current.Id, RestrictPolicy.Public), UiHelper.NewItemsSource(UserPreviewListView)); + } + } + + private void UserBrowserPageScrollViewer_OnMouseLeftButtonDown(object sender, MouseButtonEventArgs e) + { + DeactivateControl(); + } + + private void UserPrevItem_OnMouseLeftButtonDown(object sender, MouseButtonEventArgs e) + { + var usrDateContext = sender.GetDataContext(); + + SetUserBrowserContext(usrDateContext); + } + + private void SetupUserUploads(string id) + { + PixivHelper.Enumerate(new UploadAsyncEnumerable(id), UiHelper.NewItemsSource(UserIllustsImageListView)); + } + + private void SetupUserGallery(string id) + { + PixivHelper.Enumerate(AbstractGalleryAsyncEnumerable.Of(id, RestrictPolicy.Public), UiHelper.NewItemsSource(UserIllustsImageListView)); + } + + private async void UserPrevItem_OnLoaded(object sender, RoutedEventArgs e) + { + var (avatar, thumbnails) = GetUserPrevImageControls(sender); + var dataContext = sender.GetDataContext(); + + UiHelper.SetImageSource(avatar, await PixivIO.FromUrl(dataContext.Avatar)); + + var counter = 0; + foreach (var thumbnail in thumbnails) + { + if (counter < dataContext.Thumbnails.Length) + { + UiHelper.SetImageSource(thumbnail, await PixivIO.FromUrl(dataContext.Thumbnails[counter++])); + } + } + } + + private void UploadChecker_OnMouseLeftButtonDown(object sender, MouseButtonEventArgs e) + { + if (!IsAtUploadCheckerPosition()) + { + UserBrowserPageScrollViewer.GetResources("CheckerIncreaseWidthAnimation").Apply(s => s.Completed += (o, args) => + { + CheckerSnackBar.HorizontalAlignment = HorizontalAlignment.Left; + UserBrowserPageScrollViewer.GetResources("CheckerDecreaseWidthAnimation").Begin(); + }).Begin(); + } + + SetupUserUploads(sender.GetDataContext().Id); + } + + private void GalleryChecker_OnMouseLeftButtonDown(object sender, MouseButtonEventArgs e) + { + if (IsAtUploadCheckerPosition()) + { + UserBrowserPageScrollViewer.GetResources("CheckerIncreaseWidthAnimation").Apply(s => s.Completed += (o, args) => + { + CheckerSnackBar.HorizontalAlignment = HorizontalAlignment.Right; + UserBrowserPageScrollViewer.GetResources("CheckerDecreaseWidthAnimation").Begin(); + }).Begin(); + } + + SetupUserGallery(sender.GetDataContext().Id); + } + + private bool IsAtUploadCheckerPosition() + { + return CheckerSnackBar.HorizontalAlignment == HorizontalAlignment.Left && CheckerSnackBar.Width.Equals(100); + } + + private async void FollowButton_OnClick(object sender, RoutedEventArgs e) + { + var usr = sender.GetDataContext(); + await PixivClient.Instance.FollowArtist(usr, RestrictPolicy.Public); + } + + private async void UnFollowButton_OnClick(object sender, RoutedEventArgs e) + { + var usr = sender.GetDataContext(); + await PixivClient.Instance.UnFollowArtist(usr); + } + + private void ShareUserButton_OnPreviewMouseLeftButtonDown(object sender, MouseButtonEventArgs e) + { + Clipboard.SetDataObject($"https://www.pixiv.net/users/{sender.GetDataContext().Id}"); + MessageQueue.Enqueue(AkaI18N.ShareLinkCopiedToClipboard); + } + + + private void ViewUserInBrowserButton_OnPreviewMouseLeftButtonDown(object sender, MouseButtonEventArgs e) + { + Process.Start(new ProcessStartInfo("cmd", $"/c start https://www.pixiv.net/users/{sender.GetDataContext().Id}") { CreateNoWindow = true }); + } + + #endregion + + #region 作品浏览器 + + private async void SetAsWallPaperButton_OnMouseLeftButtonDown(object sender, MouseButtonEventArgs e) + { + var trans = (IllustTransitioner) IllustBrowserContainer.Children[1]; + var transitions = (IEnumerable) trans.IllustTransition.ItemsSource; + var selectedIndex = transitions.ToList()[trans.IllustTransition.SelectedIndex]; + var location = Path.Combine(AppContext.PermanentlyFolder, "wallpaper.bmp"); + var wallPaper = new WallpaperManager(location, (BitmapSource) ((IllustPresenter) selectedIndex.Content).ImgSource); + await wallPaper.Execute(); + } + + private static TransitionerSlide InitTransitionerSlide(Illustration illust) + { + return new TransitionerSlide { ForwardWipe = new FadeWipe(), BackwardWipe = new FadeWipe(), Content = new IllustPresenter(illust) }; + } + + private void IllustBrowserDialogHost_OnDialogClosing(object sender, DialogClosingEventArgs e) + { + IllustBrowserContainer.Children.RemoveAt(1); + UiHelper.ReleaseImage(IllustBrowserUserAvatar); + IllustBrowserDialogHost.DataContext = null; + } + + private async void TagNavigateHyperlink_OnClick(object sender, RoutedEventArgs e) + { + var txt = ((Tag) ((Hyperlink) sender).DataContext).Name; + + if (!UserBrowserPageScrollViewer.Opacity.Equals(0)) + { + BackToMainPageButton.RaiseEvent(new MouseButtonEventArgs(Mouse.PrimaryDevice, 0, MouseButton.Left) { RoutedEvent = Mouse.MouseDownEvent, Source = this }); + } + + IllustBrowserDialogHost.CurrentSession.Close(); + NavigatorList.SelectedItem = Instance.MenuTab; + + await Task.Delay(300); + KeywordTextBox.Text = txt; + } + + private void ShareButton_OnMouseLeftButtonDown(object sender, MouseButtonEventArgs e) + { + Clipboard.SetDataObject($"https://www.pixiv.net/artworks/{sender.GetDataContext().Id}"); + MessageQueue.Enqueue(AkaI18N.ShareLinkCopiedToClipboard); + } + + private void ImageBrowserUserAvatar_OnMouseLeftButtonDown(object sender, MouseButtonEventArgs e) + { + var usr = new User { Id = sender.GetDataContext().UserId }; + IllustBrowserDialogHost.CurrentSession.Close(); + SetUserBrowserContext(usr); + } + + private void ViewInBrowserButton_OnMouseLeftButtonDown(object sender, MouseButtonEventArgs e) + { + Process.Start(new ProcessStartInfo("cmd", $"/c start https://www.pixiv.net/artworks/{sender.GetDataContext().Id}") { CreateNoWindow = true }); + } + + private void DownloadButton_OnMouseLeftButtonDown(object sender, MouseButtonEventArgs e) + { + DownloadManager.EnqueueDownloadItem(sender.GetDataContext()); + MessageQueue.Enqueue(AkaI18N.QueuedDownload); + } + + private void IllustBrowserFavorButton_OnMouseLeftButtonDown(object sender, MouseButtonEventArgs e) + { + PixivClient.Instance.PostFavoriteAsync(sender.GetDataContext(), RestrictPolicy.Public); + } + + private void IllustBrowserPrivateFavorButton_OnMouseLeftButtonDown(object sender, MouseButtonEventArgs e) + { + PixivClient.Instance.PostFavoriteAsync(sender.GetDataContext(), RestrictPolicy.Private); + } + + private void IllustBrowserDisfavorButton_OnMouseLeftButtonDown(object sender, MouseButtonEventArgs e) + { + PixivClient.Instance.RemoveFavoriteAsync(sender.GetDataContext()); + } + + #endregion + + #region 动态 + + private async void ReferImage_OnLoaded(object sender, RoutedEventArgs e) + { + var trend = sender.GetDataContext(); + var img = (Image) sender; + if (trend.IsReferToUser) + { + img.Effect = new BlurEffect { KernelType = KernelType.Gaussian, Radius = 50, RenderingBias = RenderingBias.Quality }; + } + + UiHelper.SetImageSource(sender, await PixivIO.FromUrl(trend.TrendObjectThumbnail)); + } + + private async void ReferUserAvatar_OnLoaded(object sender, RoutedEventArgs e) + { + var trend = sender.GetDataContext(); + if (trend.IsReferToUser) + { + UiHelper.SetImageSource(sender, await PixivIO.FromUrl(trend.TrendObjectThumbnail)); + } + } + + private async void PostUserAvatar_OnLoaded(object sender, RoutedEventArgs e) + { + UiHelper.SetImageSource(sender, await PixivIO.FromUrl(sender.GetDataContext().PostUserThumbnail)); + } + + private async void TrendsTab_OnSelected(object sender, RoutedEventArgs e) + { + if (Settings.Global.Cookie.IsNullOrEmpty()) + { + await MessageDialog.Warning(WarningDialog, AkaI18N.RequiresWebCookie); + MoveUpHomePage(); + return; + } + + MoveDownHomePage(); + MessageQueue.Enqueue(AkaI18N.SearchingTrends); + + PixivHelper.Enumerate(new TrendsAsyncEnumerable(), UiHelper.NewItemsSource(TrendsListView), 20); + } + + private async void ReferImage_OnMouseLeftButtonDown(object sender, MouseButtonEventArgs e) + { + OpenIllustBrowser(await PixivHelper.IllustrationInfo(sender.GetDataContext().TrendObjectId)); + e.Handled = true; + } + + private void ReferUser_OnMouseLeftButtonDown(object sender, MouseButtonEventArgs e) + { + OpenUserBrowser(); + SetUserBrowserContext(new User { Id = sender.GetDataContext().TrendObjectId }); + e.Handled = true; + } + + private void Hyperlink_OnClick(object sender, RoutedEventArgs e) + { + OpenUserBrowser(); + SetUserBrowserContext(new User { Id = ((Trends) ((Hyperlink) sender).DataContext).PostUserId }); + e.Handled = true; + } + + #endregion + + #region 榜单 + + private void RankDatePicker_OnSelectedDateChanged(object sender, SelectionChangedEventArgs e) + { + if (Navigating(RankingTab)) + { + GetRanking(); + } + } + + private void RankOptionPicker_OnSelectionChanged(object sender, SelectionChangedEventArgs e) + { + if (Navigating(RankingTab)) + { + GetRanking(); + } + } + + private void GetRanking() + { + var option = RankOptionPicker.SelectedItem.GetDataContext().First(p => p.IsSelected); + var dateTime = RankDatePicker.SelectedDate; + + if (option.Corresponding.AttributeAttached() && Settings.Global.ExcludeTag.Any(t => t.ToUpper() == "R-18" || t.ToUpper() == "R-18G")) + { + MessageQueue.Enqueue(AkaI18N.RankNeedR18On); + UiHelper.NewItemsSource(ImageListView); + return; + } + + if (dateTime is { } time) + { + PixivHelper.Enumerate(new RankingAsyncEnumerable(option.Corresponding, time), UiHelper.NewItemsSource(ImageListView)); + return; + } + + MessageQueue.Enqueue(AkaI18N.RankDateCannotBeNull); + } + + private void RankingTab_OnSelected(object sender, RoutedEventArgs e) + { + GetRanking(); + } + + #endregion + + #region 工具 + + private bool disableKeyEvent; + + private void TopBarRetract(TranslateTransform transform) + { + if (transform.Y > -60) + { + var animation = new DoubleAnimation(transform.Y, -60, TimeSpan.FromMilliseconds(300)) { EasingFunction = new CubicEase() }; + animation.Completed += (o, args) => animating = false; + animating = true; + transform.BeginAnimation(TranslateTransform.YProperty, animation); + } + } + + private void TopBarExpand(TranslateTransform transform) + { + if (transform.Y < 0) + { + var animation = new DoubleAnimation(transform.Y, 0, TimeSpan.FromMilliseconds(300)) { EasingFunction = new CubicEase() }; + animation.Completed += (o, args) => animating = false; + animating = true; + transform.BeginAnimation(TranslateTransform.YProperty, animation); + } + } + + private async Task AcquireRecommendUser() + { + var list = UiHelper.NewItemsSource(RecommendIllustratorListBox); + list.AddRange(await RecommendIllustratorDeferrer.Instance.Acquire(6)); + } + + private IEnumerable GetImageSourceCopy() + { + var lst = (IEnumerable) (BrowsingUser() ? UserIllustsImageListView : ImageListView)?.ItemsSource; + return lst != null ? lst.ToList() : new List(); + } + + private void MoveUpHomePage() + { + NavigateTo(MenuTab); + DoQueryButton.Enable(); + if (!((TranslateTransform) HomeDisplayContainer.RenderTransform).Y.Equals(0)) + { + HomeDisplayContainer.GetResources("MoveUpAnimation")?.Begin(); + } + } + + private void MoveDownHomePage() + { + MenuTab.IsSelected = false; + DoQueryButton.Disable(); + if (((TranslateTransform) HomeDisplayContainer.RenderTransform).Y.Equals(0)) + { + HomeDisplayContainer.GetResources("MoveDownAnimation").Begin(); + } + } + + private bool BrowsingUser() + { + return !UserBrowserPageScrollViewer.Opacity.Equals(0D); + } + + private static (Image avatar, Image[] thumbnails) GetUserPrevImageControls(object sender) + { + var list = ((Card) sender).FindVisualChildren().ToArray(); + + return (list.First(p => p.Name == "UserAvatar"), list.Where(p => p.Name != "UserAvatar").ToArray()); + } + + public async void SetUserBrowserContext(User user) + { + var usr = await HttpClientFactory.AppApiService().GetUserInformation(new UserInformationRequest { Id = $"{user.Id}" }); + var usrEntity = new User + { + Avatar = usr.UserEntity.ProfileImageUrls.Medium, + Background = usr.UserEntity.ProfileImageUrls.Medium, + Follows = (int) usr.UserProfile.TotalFollowUsers, + Id = usr.UserEntity.Id.ToString(), + Introduction = usr.UserEntity.Comment, + IsFollowed = usr.UserEntity.IsFollowed, + IsPremium = usr.UserProfile.IsPremium, + Name = usr.UserEntity.Name, + Thumbnails = user.Thumbnails + }; + PixivHelper.RecordTimelineInternal(new BrowsingHistory + { + BrowseObjectId = usrEntity.Id, + BrowseObjectState = usrEntity.Name, + BrowseObjectThumbnail = usrEntity.Avatar, + IsReferToUser = true, + Type = "user" + }); + UserBrowserPageScrollViewer.DataContext = usrEntity; + UiHelper.SetImageSource(UserBrowserUserAvatar, await PixivIO.FromUrl(usrEntity.Avatar)); + SetupUserUploads(usrEntity.Id); + } + + public void OpenUserBrowser() + { + this.GetResources("OpenUserBrowserAnimation").Begin(); + } + + private void SetIllustBrowserIllustrationDataContext(Illustration illustration, bool record = true) + { + if (!illustration.FromSpotlight && record) + { + PixivHelper.RecordTimelineInternal(new BrowsingHistory + { + BrowseObjectId = illustration.Id, + BrowseObjectState = illustration.Title, + BrowseObjectThumbnail = illustration.Thumbnail, + IllustratorName = illustration.UserName, + IsReferToIllust = true, + Type = "illust" + }); + } + + IllustBrowserDialogHost.DataContext = illustration; + disableKeyEvent = true; + Task.WhenAll(Task.Run(async () => + { + var userInfo = await HttpClientFactory.AppApiService().GetUserInformation(new UserInformationRequest { Id = illustration.UserId }); + if (await PixivIO.FromUrl(userInfo.UserEntity.ProfileImageUrls.Medium) is { } avatar) + { + UiHelper.SetImageSource(IllustBrowserUserAvatar, avatar); + } + }), Task.Run(async () => + { + var list = new ObservableCollection(); + if (illustration.IsManga) + { + illustration = await PixivHelper.IllustrationInfo(illustration.Id); + } + Application.Current.Dispatcher.Invoke(() => + { + var template = new IllustTransitioner(list); + + if (illustration.IsManga) + { + list.AddRange(illustration.MangaMetadata.Select(InitTransitionerSlide)); + } + else + { + list.Add(InitTransitionerSlide(illustration)); + } + if (IllustBrowserContainer.Children[1] is IllustTransitioner) + { + IllustBrowserContainer.Children.RemoveAt(1); + } + IllustBrowserContainer.Children.Insert(1, template); + }); + })).ContinueWith(_ => disableKeyEvent = false, TaskScheduler.FromCurrentSynchronizationContext()); + } + + public async void OpenIllustBrowser(Illustration illustration, bool record = true) + { + SetIllustBrowserIllustrationDataContext(illustration, record); + await Task.Delay(100); + IllustBrowserDialogHost.OpenControl(); + } + + private bool Navigating(ListViewItem item) + { + return NavigatorList.SelectedItem?.Equals(item) is true; + } + + private void NavigateTo(ListViewItem item) + { + NavigatorList.SelectedItem = item; + } + + #endregion + } +} \ No newline at end of file diff --git a/Pixeval/UI/SignIn.xaml b/Pixeval/UI/SignIn.xaml new file mode 100644 index 00000000..61b8dfc3 --- /dev/null +++ b/Pixeval/UI/SignIn.xaml @@ -0,0 +1,153 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/Pixeval/UI/UserControls/UserPreviewPopupContent.xaml.cs b/Pixeval/UI/UserControls/UserPreviewPopupContent.xaml.cs new file mode 100644 index 00000000..d703cb59 --- /dev/null +++ b/Pixeval/UI/UserControls/UserPreviewPopupContent.xaml.cs @@ -0,0 +1,65 @@ +#region Copyright (C) 2019-2020 Dylech30th. All rights reserved. + +// Pixeval - A Strong, Fast and Flexible Pixiv Client +// Copyright (C) 2019-2020 Dylech30th +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as +// published by the Free Software Foundation, either version 3 of the +// License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see . + +#endregion + +using System.Windows; +using System.Windows.Media.Imaging; +using Pixeval.Core; +using Pixeval.Data.ViewModel; +using Pixeval.Data.Web.Delegation; +using Pixeval.Objects.Primitive; + +namespace Pixeval.UI.UserControls +{ + public partial class UserPreviewPopupContent + { + public UserPreviewPopupContent() + { + InitializeComponent(); + } + + public async void SetImages(BitmapImage left, BitmapImage center, BitmapImage right) + { + UiHelper.SetImageSource(ImgLeft, left); + UiHelper.SetImageSource(ImgCenter, center); + UiHelper.SetImageSource(ImgRight, right); + UiHelper.SetImageSource(UserAvatar, await PixivIO.FromUrl(this.GetDataContext().Avatar)); + try + { + UiHelper.SetImageSource(Banner, await PixivIO.FromUrl((await HttpClientFactory.WebApiService().GetWebApiUserDetail(this.GetDataContext().Id)).ResponseBody.UserDetails.CoverImage.ProfileCoverImage.The720X360)); + } + catch + { + /* ignore */ + } + } + + private async void FollowButton_OnClick(object sender, RoutedEventArgs e) + { + var usr = sender.GetDataContext(); + await PixivClient.Instance.FollowArtist(usr, RestrictPolicy.Public); + } + + private async void UnFollowButton_OnClick(object sender, RoutedEventArgs e) + { + var usr = sender.GetDataContext(); + await PixivClient.Instance.UnFollowArtist(usr); + } + } +} \ No newline at end of file diff --git a/Pixeval/app.manifest b/Pixeval/app.manifest new file mode 100644 index 00000000..b90e8a86 --- /dev/null +++ b/Pixeval/app.manifest @@ -0,0 +1,76 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/Pixeval/build.ps1 b/Pixeval/build.ps1 new file mode 100644 index 00000000..5c81abc6 --- /dev/null +++ b/Pixeval/build.ps1 @@ -0,0 +1 @@ +dotnet publish /p:Platform=x64 Pixeval.csproj -c Release -f netcoreapp3.1 --no-self-contained -r win-x64 \ No newline at end of file