This commit is contained in:
Rinacm 2020-10-29 17:52:44 +08:00
parent 4a349f82e5
commit 3995577133
321 changed files with 156 additions and 25219 deletions

4
.idea/.idea.Pixeval/.idea/encodings.xml generated Normal file
View File

@ -0,0 +1,4 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="Encoding" addBOMForNewFiles="with BOM under Windows, with no BOM otherwise" />
</project>

View File

@ -0,0 +1,8 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="ContentModelUserStore">
<attachedFolders />
<explicitIncludes />
<explicitExcludes />
</component>
</project>

8
.idea/.idea.Pixeval/.idea/modules.xml generated Normal file
View File

@ -0,0 +1,8 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="ProjectModuleManager">
<modules>
<module fileurl="file://$PROJECT_DIR$/.idea/.idea.Pixeval/riderModule.iml" filepath="$PROJECT_DIR$/.idea/.idea.Pixeval/riderModule.iml" />
</modules>
</component>
</project>

View File

@ -0,0 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="RiderProjectSettingsUpdater">
<option name="vcsConfiguration" value="1" />
</component>
</project>

View File

@ -1,6 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="VcsDirectoryMappings">
<mapping directory="$PROJECT_DIR$/../.." vcs="Git" />
<mapping directory="$PROJECT_DIR$" vcs="Git" />
</component>
</project>

36
.idea/.idea.Pixeval/riderModule.iml generated Normal file
View File

@ -0,0 +1,36 @@
<?xml version="1.0" encoding="UTF-8"?>
<module type="RIDER_MODULE" version="4">
<component name="NewModuleRootManager">
<content url="file://$MODULE_DIR$/../.." />
<content url="file://$USER_HOME$/.nuget/packages/cef.redist.x64/83.4.2/CEF/README.txt" />
<content url="file://$USER_HOME$/.nuget/packages/cef.redist.x64/83.4.2/CEF/cef.pak" />
<content url="file://$USER_HOME$/.nuget/packages/cef.redist.x64/83.4.2/CEF/cef_100_percent.pak" />
<content url="file://$USER_HOME$/.nuget/packages/cef.redist.x64/83.4.2/CEF/cef_200_percent.pak" />
<content url="file://$USER_HOME$/.nuget/packages/cef.redist.x64/83.4.2/CEF/cef_extensions.pak" />
<content url="file://$USER_HOME$/.nuget/packages/cef.redist.x64/83.4.2/CEF/chrome_elf.dll" />
<content url="file://$USER_HOME$/.nuget/packages/cef.redist.x64/83.4.2/CEF/d3dcompiler_47.dll" />
<content url="file://$USER_HOME$/.nuget/packages/cef.redist.x64/83.4.2/CEF/devtools_resources.pak" />
<content url="file://$USER_HOME$/.nuget/packages/cef.redist.x64/83.4.2/CEF/icudtl.dat" />
<content url="file://$USER_HOME$/.nuget/packages/cef.redist.x64/83.4.2/CEF/libEGL.dll" />
<content url="file://$USER_HOME$/.nuget/packages/cef.redist.x64/83.4.2/CEF/libGLESv2.dll" />
<content url="file://$USER_HOME$/.nuget/packages/cef.redist.x64/83.4.2/CEF/libcef.dll" />
<content url="file://$USER_HOME$/.nuget/packages/cef.redist.x64/83.4.2/CEF/locales" />
<content url="file://$USER_HOME$/.nuget/packages/cef.redist.x64/83.4.2/CEF/snapshot_blob.bin" />
<content url="file://$USER_HOME$/.nuget/packages/cef.redist.x64/83.4.2/CEF/swiftshader" />
<content url="file://$USER_HOME$/.nuget/packages/cef.redist.x64/83.4.2/CEF/v8_context_snapshot.bin" />
<content url="file://$USER_HOME$/.nuget/packages/cefsharp.common/83.4.20/CefSharp/x64/CefSharp.BrowserSubprocess.Core.dll" />
<content url="file://$USER_HOME$/.nuget/packages/cefsharp.common/83.4.20/CefSharp/x64/CefSharp.BrowserSubprocess.Core.pdb" />
<content url="file://$USER_HOME$/.nuget/packages/cefsharp.common/83.4.20/CefSharp/x64/CefSharp.BrowserSubprocess.exe" />
<content url="file://$USER_HOME$/.nuget/packages/cefsharp.common/83.4.20/CefSharp/x64/CefSharp.BrowserSubprocess.pdb" />
<content url="file://$USER_HOME$/.nuget/packages/cefsharp.common/83.4.20/CefSharp/x64/CefSharp.Core.dll" />
<content url="file://$USER_HOME$/.nuget/packages/cefsharp.common/83.4.20/CefSharp/x64/CefSharp.Core.pdb" />
<content url="file://$USER_HOME$/.nuget/packages/cefsharp.common/83.4.20/CefSharp/x64/CefSharp.Core.xml" />
<content url="file://$USER_HOME$/.nuget/packages/cefsharp.common/83.4.20/CefSharp/x64/CefSharp.XML" />
<content url="file://$USER_HOME$/.nuget/packages/cefsharp.common/83.4.20/CefSharp/x64/CefSharp.dll" />
<content url="file://$USER_HOME$/.nuget/packages/cefsharp.common/83.4.20/CefSharp/x64/CefSharp.pdb" />
<content url="file://$USER_HOME$/.nuget/packages/cefsharp.wpf/83.4.20/CefSharp/x64/CefSharp.Wpf.XML" />
<content url="file://$USER_HOME$/.nuget/packages/cefsharp.wpf/83.4.20/CefSharp/x64/CefSharp.Wpf.dll" />
<content url="file://$USER_HOME$/.nuget/packages/cefsharp.wpf/83.4.20/CefSharp/x64/CefSharp.Wpf.pdb" />
<orderEntry type="sourceFolder" forTests="false" />
</component>
</module>

View File

@ -1,9 +1,17 @@
<Window x:Class="Pixeval.Updater.MainWindow" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
<Window x:Class="Pixeval.Updater.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:materialDesign="http://materialdesigninxaml.net/winfx/xaml/themes" Title="MainWindow" Width="600"
Height="100" AllowsTransparency="True" Background="Transparent"
Icon="Resources/pxlogo.ico" Loaded="MainWindow_OnLoaded" MouseDown="MainWindow_OnMouseDown"
ResizeMode="NoResize" WindowStyle="None">
xmlns:materialDesign="http://materialdesigninxaml.net/winfx/xaml/themes"
Title="MainWindow"
Width="600"
Height="100"
AllowsTransparency="True"
Background="Transparent"
Icon="Resources/pxlogo.ico"
Loaded="MainWindow_OnLoaded"
MouseDown="MainWindow_OnMouseDown"
ResizeMode="NoResize"
WindowStyle="None">
<Window.Resources>
<Style x:Key="RoundedCornerProgressBar" TargetType="{x:Type ProgressBar}">
<Setter Property="Foreground" Value="{DynamicResource PrimaryHueDarkBrush}" />
@ -34,7 +42,8 @@
</Storyboard>
<Storyboard x:Key="OnLoadedNoAnimation">
<DoubleAnimation Storyboard.TargetName="TemplateRoot"
Storyboard.TargetProperty="(UIElement.Opacity)" To="1"
Storyboard.TargetProperty="(UIElement.Opacity)"
To="1"
Duration="0" />
<DoubleAnimation Storyboard.TargetName="TemplateRoot"
Storyboard.TargetProperty="(UIElement.RenderTransform).(TransformGroup.Children)[0].(ScaleTransform.ScaleX)"
@ -46,7 +55,9 @@
Duration="0" />
</Storyboard>
</ControlTemplate.Resources>
<Grid x:Name="TemplateRoot" Opacity="0" RenderTransformOrigin="0,0.5">
<Grid x:Name="TemplateRoot"
Opacity="0"
RenderTransformOrigin="0,0.5">
<Grid.RenderTransform>
<TransformGroup>
<ScaleTransform ScaleX="0" ScaleY="0" />
@ -60,13 +71,18 @@
BorderThickness="{TemplateBinding BorderThickness}"
CornerRadius="3" />
<Rectangle x:Name="PART_Track" />
<Grid x:Name="PART_Indicator" HorizontalAlignment="Left" ClipToBounds="true">
<Grid x:Name="PART_Indicator"
HorizontalAlignment="Left"
ClipToBounds="true">
<Rectangle x:Name="Indicator"
Fill="{TemplateBinding Foreground}"
RadiusX="3" RadiusY="3" />
RadiusX="3"
RadiusY="3" />
<Rectangle x:Name="Animation"
Fill="{TemplateBinding Foreground}"
RadiusX="3" RadiusY="3" RenderTransformOrigin="0.5,0.5">
RadiusX="3"
RadiusY="3"
RenderTransformOrigin="0.5,0.5">
<Rectangle.RenderTransform>
<TransformGroup>
<ScaleTransform />
@ -139,27 +155,46 @@
</Setter>
</Style>
</Window.Resources>
<materialDesign:Card materialDesign:ShadowAssist.ShadowDepth="Depth4" ClipToBounds="True" UniformCornerRadius="10">
<materialDesign:Card materialDesign:ShadowAssist.ShadowDepth="Depth4"
ClipToBounds="True"
UniformCornerRadius="10">
<Grid>
<Grid.RowDefinitions>
<RowDefinition />
<RowDefinition />
<RowDefinition />
</Grid.RowDefinitions>
<TextBlock Margin="20,0,0,0" VerticalAlignment="Bottom" FontFamily="Microsoft YaHei Light"
<TextBlock Margin="20,0,0,0"
VerticalAlignment="Bottom"
FontFamily="Microsoft YaHei Light"
Text="正在下载更新..." />
<StackPanel Grid.Row="1" FlowDirection="LeftToRight" Orientation="Horizontal">
<ProgressBar x:Name="DownloadProgressIndicator" Width="520" Margin="20,0,0,0"
Background="#ABCCEA" BorderThickness="0" Foreground="#3386F4"
Maximum="1" Minimum="0"
<StackPanel Grid.Row="1"
FlowDirection="LeftToRight"
Orientation="Horizontal">
<ProgressBar x:Name="DownloadProgressIndicator"
Width="520"
Margin="20,0,0,0"
Background="#ABCCEA"
BorderThickness="0"
Foreground="#007CEE"
Maximum="1"
Minimum="0"
Style="{StaticResource RoundedCornerProgressBar}"
ValueChanged="DownloadProgressIndicator_OnValueChanged" />
<materialDesign:PackIcon x:Name="CancelButton" Margin="10,0,0,0" VerticalAlignment="Center"
Cursor="Hand" Foreground="Gray" Kind="CloseCircle"
MouseLeftButtonDown="CancelButton_OnMouseLeftButtonDown" ToolTip="取消" />
<materialDesign:PackIcon x:Name="CancelButton"
Margin="10,0,0,0"
VerticalAlignment="Center"
Cursor="Hand"
Foreground="Gray"
Kind="CloseCircle"
MouseLeftButtonDown="CancelButton_OnMouseLeftButtonDown"
ToolTip="取消" />
</StackPanel>
<TextBlock x:Name="ProgressIndicatorText" Grid.Row="1" Grid.RowSpan="2"
Margin="20,0,0,0" VerticalAlignment="Center" />
<TextBlock x:Name="ProgressIndicatorText"
Grid.Row="1"
Grid.RowSpan="2"
Margin="20,0,0,0"
VerticalAlignment="Center" />
</Grid>
</materialDesign:Card>
</Window>

View File

@ -40,20 +40,23 @@ namespace Pixeval.Updater
/// </summary>
public partial class MainWindow
{
private static readonly CancellationTokenSource _cancellationTokenSource = new CancellationTokenSource();
private static readonly CancellationTokenSource CancellationTokenSource = new CancellationTokenSource();
private static readonly string _currentDir = Path.GetDirectoryName(new Uri(Assembly.GetExecutingAssembly().CodeBase!).LocalPath);
private static readonly string CurrentDir = Path.GetDirectoryName(new Uri(Assembly.GetExecutingAssembly().CodeBase!).LocalPath);
private static readonly DirectoryInfo _pixevalDirectory = Directory.GetParent(_currentDir);
private static readonly DirectoryInfo PixevalDirectory = Directory.GetParent(CurrentDir);
private static readonly string _zipTmpPath = Path.Combine(_pixevalDirectory.FullName, "Pixeval.zip");
private static readonly string ZipTmpPath = Path.Combine(PixevalDirectory.FullName, "Pixeval.zip");
private static readonly string _extractedTmpPath = Path.Combine(_pixevalDirectory.FullName, "Pixeval");
private static readonly string ExtractedTmpPath = Path.Combine(PixevalDirectory.FullName, "Pixeval");
public MainWindow()
{
InitializeComponent();
if (!CheckIsPixevalDirectory(_pixevalDirectory)) Exit("Pixeval更新器必须位于Pixeval目录中");
if (!CheckIsPixevalDirectory(PixevalDirectory))
{
Exit("Pixeval更新器必须位于Pixeval目录中");
}
}
private void MainWindow_OnMouseDown(object sender, MouseButtonEventArgs e)
@ -65,7 +68,7 @@ namespace Pixeval.Updater
{
try
{
await using var memory = await Download(RelevantUrl.ZipArchive, new Progress<double>(p => DownloadProgressIndicator.Value = p), _cancellationTokenSource.Token);
await using var memory = await Download(RelevantUrl.ZipArchive, new Progress<double>(p => DownloadProgressIndicator.Value = p), CancellationTokenSource.Token);
memory.Position = 0L;
if (memory.Checksum<SHA256Managed>() != (await GetRemoteChecksum()).ToLower())
{
@ -74,17 +77,17 @@ namespace Pixeval.Updater
}
RmFiles();
await using (var fileStream = new FileStream(_zipTmpPath, FileMode.Create, FileAccess.ReadWrite, FileShare.None))
await using (var fileStream = new FileStream(ZipTmpPath, FileMode.Create, FileAccess.ReadWrite, FileShare.None))
{
memory.WriteTo(fileStream);
}
Extract();
File.Delete(_zipTmpPath);
Directory.Delete(Path.Combine(_extractedTmpPath, "updater"), true);
FileSystem.CopyDirectory(_extractedTmpPath, _pixevalDirectory.FullName);
Directory.Delete(_extractedTmpPath, true);
Process.Start(Path.Combine(_pixevalDirectory.FullName, "Pixeval.exe"));
File.Delete(ZipTmpPath);
Directory.Delete(Path.Combine(ExtractedTmpPath, "updater"), true);
FileSystem.CopyDirectory(ExtractedTmpPath, PixevalDirectory.FullName);
Directory.Delete(ExtractedTmpPath, true);
Process.Start(Path.Combine(PixevalDirectory.FullName, "Pixeval.exe"));
Exit();
}
catch (TaskCanceledException)
@ -104,22 +107,31 @@ namespace Pixeval.Updater
private static void RmFiles()
{
var rmList = _pixevalDirectory.GetFileSystemInfos().Where(fs => fs.FullName != _currentDir);
var rmList = PixevalDirectory.GetFileSystemInfos().Where(fs => fs.FullName != CurrentDir);
foreach (var fileSystemInfo in rmList)
{
if (File.GetAttributes(fileSystemInfo.FullName).HasFlag(FileAttributes.Directory))
{
Directory.Delete(fileSystemInfo.FullName, true);
}
else
{
fileSystemInfo.Delete();
}
}
}
private static void Extract()
{
ZipFile.ExtractToDirectory(_zipTmpPath, _pixevalDirectory.FullName);
ZipFile.ExtractToDirectory(ZipTmpPath, PixevalDirectory.FullName);
}
private void Exit(string message = null)
{
if (message != null) MessageBox.Show(message);
if (message != null)
{
MessageBox.Show(message);
}
Close();
Environment.Exit(0);
@ -132,7 +144,7 @@ namespace Pixeval.Updater
private void CancelButton_OnMouseLeftButtonDown(object sender, MouseButtonEventArgs e)
{
_cancellationTokenSource.Cancel();
CancellationTokenSource.Cancel();
}
private static bool CheckIsPixevalDirectory(DirectoryInfo dir)
@ -145,7 +157,10 @@ namespace Pixeval.Updater
using var response = await GetResponseHeader(new HttpClient(), url);
var contentLength = response.Content.Headers.ContentLength;
if (!contentLength.HasValue) throw new InvalidOperationException("cannot retrieve the content length of the request uri");
if (!contentLength.HasValue)
{
throw new InvalidOperationException("cannot retrieve the content length of the request uri");
}
response.EnsureSuccessStatusCode();

View File

Before

Width:  |  Height:  |  Size: 205 KiB

After

Width:  |  Height:  |  Size: 205 KiB

View File

Before

Width:  |  Height:  |  Size: 205 KiB

After

Width:  |  Height:  |  Size: 205 KiB

View File

@ -3,11 +3,9 @@ Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio Version 16
VisualStudioVersion = 16.0.30104.148
MinimumVisualStudioVersion = 10.0.40219.1
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Pixeval", "src\Pixeval\Pixeval.csproj", "{3F5E9BB6-B8F1-453F-89E9-F719342D1E16}"
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Pixeval", "Pixeval\Pixeval.csproj", "{3F5E9BB6-B8F1-453F-89E9-F719342D1E16}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Pixeval.Updater", "src\Pixeval.Updater\Pixeval.Updater.csproj", "{D328EB05-39B1-46A9-B874-113F362DD589}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Pixeval.Interchange", "src\Pixeval.Interchange\Pixeval.Interchange.csproj", "{AF5AF2B2-4C1D-4103-AA97-D219C4768564}"
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Pixeval.Updater", "Pixeval.Updater\Pixeval.Updater.csproj", "{D328EB05-39B1-46A9-B874-113F362DD589}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{D044DAED-2EF7-43B0-9C75-7714DC021F3B}"
EndProject
@ -42,18 +40,6 @@ Global
{D328EB05-39B1-46A9-B874-113F362DD589}.Release|x64.Build.0 = Release|Any CPU
{D328EB05-39B1-46A9-B874-113F362DD589}.Release|x86.ActiveCfg = Release|Any CPU
{D328EB05-39B1-46A9-B874-113F362DD589}.Release|x86.Build.0 = Release|Any CPU
{AF5AF2B2-4C1D-4103-AA97-D219C4768564}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{AF5AF2B2-4C1D-4103-AA97-D219C4768564}.Debug|Any CPU.Build.0 = Debug|Any CPU
{AF5AF2B2-4C1D-4103-AA97-D219C4768564}.Debug|x64.ActiveCfg = Debug|Any CPU
{AF5AF2B2-4C1D-4103-AA97-D219C4768564}.Debug|x64.Build.0 = Debug|Any CPU
{AF5AF2B2-4C1D-4103-AA97-D219C4768564}.Debug|x86.ActiveCfg = Debug|Any CPU
{AF5AF2B2-4C1D-4103-AA97-D219C4768564}.Debug|x86.Build.0 = Debug|Any CPU
{AF5AF2B2-4C1D-4103-AA97-D219C4768564}.Release|Any CPU.ActiveCfg = Release|Any CPU
{AF5AF2B2-4C1D-4103-AA97-D219C4768564}.Release|Any CPU.Build.0 = Release|Any CPU
{AF5AF2B2-4C1D-4103-AA97-D219C4768564}.Release|x64.ActiveCfg = Release|Any CPU
{AF5AF2B2-4C1D-4103-AA97-D219C4768564}.Release|x64.Build.0 = Release|Any CPU
{AF5AF2B2-4C1D-4103-AA97-D219C4768564}.Release|x86.ActiveCfg = Release|Any CPU
{AF5AF2B2-4C1D-4103-AA97-D219C4768564}.Release|x86.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE

View File

@ -32,7 +32,7 @@ namespace Pixeval
{
public const string AppIdentifier = "Pixeval";
public const string CurrentVersion = "3.0.1";
public const string CurrentVersion = "3.0.2";
public const string ConfigurationFileName = "pixeval_conf.json";

View File

@ -18,6 +18,7 @@
<PropertyGroup>
<GenerateSerializationAssemblies>Off</GenerateSerializationAssemblies>
<ApplicationManifest>app.manifest</ApplicationManifest>
<Version>3.0.2</Version>
</PropertyGroup>
<ItemGroup>

View File

@ -1,12 +0,0 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>WinExe</OutputType>
<TargetFramework>netcoreapp3.1</TargetFramework>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|AnyCPU'">
<PlatformTarget>x64</PlatformTarget>
</PropertyGroup>
</Project>

View File

@ -1,61 +0,0 @@
#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 <https://www.gnu.org/licenses/>.
#endregion
using System.Diagnostics;
using System.Linq;
using System.Net.Http;
using System.Text;
using System.Text.RegularExpressions;
using System.Threading.Tasks;
namespace Pixeval.Interchange
{
public class Program
{
private const string IllustRegex = "pixeval://(www\\.)?pixiv\\.net/artworks/(?<id>\\d+)";
private const string UserRegex = "pixeval://(www\\.)?pixiv\\.net/users/(?<id>\\d+)";
private const string SpotlightRegex = "pixeval://(www\\.)?pixivision\\.net/\\w{2}/a/(?<id>\\d+)";
private static readonly HttpClient _httpClient = new HttpClient();
public static async Task Main(string[] args)
{
using (_httpClient)
{
if (!args.Any()) return;
var url = args[0];
// check protocol rationality
if (!(Regex.IsMatch(url, IllustRegex) || Regex.IsMatch(url, UserRegex) || Regex.IsMatch(url, SpotlightRegex))) return;
// check if there's an instance is running
if (PixevalInstanceRunning())
// send Pixeval custom pluggable protocol if so
await _httpClient.PostAsync("http://127.0.0.1:12547/open", new StringContent(url, Encoding.UTF8));
else
// otherwise execute Pixeval and pass the protocol as argument
Process.Start("Pixeval", url);
}
}
private static bool PixevalInstanceRunning()
{
return Process.GetProcessesByName("Pixeval").Length > 0;
}
}
}

View File

@ -1,11 +0,0 @@
{
"profiles": {
"Pixeval.Interchange": {
"commandName": "Project",
"nativeDebugging": false
},
"newProfile1": {
"commandName": "Executable"
}
}
}

View File

@ -1 +0,0 @@
dotnet publish /p:Platform=x64 Pixeval.Interchange.csproj -c Release -f netcoreapp3.1 --no-self-contained -r win-x64

File diff suppressed because it is too large Load Diff

View File

@ -1,307 +0,0 @@
<!--
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 <https://www.gnu.org/licenses/>.-->
<Application x:Class="Pixeval.App"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:materialDesign="http://materialdesigninxaml.net/winfx/xaml/themes"
xmlns:system="clr-namespace:System;assembly=System.Runtime"
xmlns:valueConverters="clr-namespace:Pixeval.Objects.ValueConverters"
StartupUri="UI/SignIn.xaml">
<Application.Resources>
<ResourceDictionary>
<ResourceDictionary.MergedDictionaries>
<ResourceDictionary
Source="pack://application:,,,/MaterialDesignThemes.Wpf;component/Themes/MaterialDesignTheme.Light.xaml" />
<ResourceDictionary
Source="pack://application:,,,/MaterialDesignThemes.Wpf;component/Themes/MaterialDesignTheme.Defaults.xaml" />
<ResourceDictionary
Source="pack://application:,,,/MaterialDesignColors;component/Themes/Recommended/Primary/MaterialDesignColor.DeepPurple.xaml" />
<ResourceDictionary
Source="pack://application:,,,/MaterialDesignColors;component/Themes/Recommended/Accent/MaterialDesignColor.Lime.xaml" />
</ResourceDictionary.MergedDictionaries>
<system:Double x:Key="Two">
2
</system:Double>
<CubicEase x:Key="CubicEase" EasingMode="EaseOut" />
<PowerEase x:Key="QuinticEase"
EasingMode="EaseInOut"
Power="1" />
<valueConverters:EnumToStringConverter x:Key="EnumToStringConverter" />
<valueConverters:VisibleIfTrueConverter x:Key="VisibleIfTrueConverter" />
<valueConverters:VisibleIfFalseConverter x:Key="VisibleIfFalseConverter" />
<valueConverters:DoublePlusConverter x:Key="DoublePlusConverter" />
<valueConverters:IllustSubscriptConverter x:Key="IllustSubscriptConverter" />
<valueConverters:DateTimeOffsetConverter x:Key="DateTimeOffsetConverter" />
<valueConverters:DoubleDivisionConverter x:Key="DoubleDivisionConverter" />
<valueConverters:BoolToCachingPolicyConverter x:Key="BoolToCachingPolicyConverter" />
<valueConverters:IllustrationMatchConditionMaskConverter x:Key="IllustrationMatchConditionMaskConverter" />
<valueConverters:InverseBooleanConverter x:Key="InverseBooleanConverter" />
<valueConverters:TrendsStatConverter x:Key="TrendsStatConverter" />
<valueConverters:DateTimeConverter x:Key="DateTimeConverter" />
<valueConverters:MultiCultureConverter x:Key="MultiCultureConverter" />
<valueConverters:TagMatchEnumToModelConverter x:Key="TagMatchEnumToModelConverter" />
<Style x:Key="RoundedCornerProgressBar" TargetType="{x:Type ProgressBar}">
<Setter Property="Foreground" Value="{DynamicResource PrimaryHueDarkBrush}" />
<Setter Property="Background" Value="{DynamicResource PrimaryHueLightBrush}" />
<Setter Property="BorderBrush" Value="{DynamicResource PrimaryHueLightBrush}" />
<Setter Property="BorderThickness" Value="1" />
<Setter Property="Height" Value="4" />
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type ProgressBar}">
<ControlTemplate.Resources>
<Storyboard x:Key="OnLoaded">
<DoubleAnimationUsingKeyFrames Storyboard.TargetName="TemplateRoot"
Storyboard.TargetProperty="(UIElement.Opacity)">
<EasingDoubleKeyFrame KeyTime="0" Value="0" />
<EasingDoubleKeyFrame KeyTime="0:0:1" Value="1" />
</DoubleAnimationUsingKeyFrames>
<DoubleAnimationUsingKeyFrames Storyboard.TargetName="TemplateRoot"
Storyboard.TargetProperty="(UIElement.RenderTransform).(TransformGroup.Children)[0].(ScaleTransform.ScaleX)">
<EasingDoubleKeyFrame KeyTime="0" Value="0" />
<EasingDoubleKeyFrame KeyTime="0:0:1" Value="1" />
</DoubleAnimationUsingKeyFrames>
<DoubleAnimationUsingKeyFrames Storyboard.TargetName="TemplateRoot"
Storyboard.TargetProperty="(UIElement.RenderTransform).(TransformGroup.Children)[0].(ScaleTransform.ScaleY)">
<EasingDoubleKeyFrame KeyTime="0" Value="0" />
<EasingDoubleKeyFrame KeyTime="0:0:1" Value="1" />
</DoubleAnimationUsingKeyFrames>
</Storyboard>
<Storyboard x:Key="OnLoadedNoAnimation">
<DoubleAnimation Storyboard.TargetName="TemplateRoot"
Storyboard.TargetProperty="(UIElement.Opacity)"
To="1"
Duration="0" />
<DoubleAnimation Storyboard.TargetName="TemplateRoot"
Storyboard.TargetProperty="(UIElement.RenderTransform).(TransformGroup.Children)[0].(ScaleTransform.ScaleX)"
To="1"
Duration="0" />
<DoubleAnimation Storyboard.TargetName="TemplateRoot"
Storyboard.TargetProperty="(UIElement.RenderTransform).(TransformGroup.Children)[0].(ScaleTransform.ScaleY)"
To="1"
Duration="0" />
</Storyboard>
</ControlTemplate.Resources>
<Grid x:Name="TemplateRoot"
Opacity="0"
RenderTransformOrigin="0,0.5">
<Grid.RenderTransform>
<TransformGroup>
<ScaleTransform ScaleX="0" ScaleY="0" />
<SkewTransform />
<RotateTransform />
<TranslateTransform />
</TransformGroup>
</Grid.RenderTransform>
<Border Background="{TemplateBinding Background}"
BorderBrush="{TemplateBinding BorderBrush}"
BorderThickness="{TemplateBinding BorderThickness}"
CornerRadius="3" />
<Rectangle x:Name="PART_Track" />
<Grid x:Name="PART_Indicator"
HorizontalAlignment="Left"
ClipToBounds="true">
<Rectangle x:Name="Indicator"
Fill="{TemplateBinding Foreground}"
RadiusX="3"
RadiusY="3" />
<Rectangle x:Name="Animation"
Fill="{TemplateBinding Foreground}"
RadiusX="3"
RadiusY="3"
RenderTransformOrigin="0.5,0.5">
<Rectangle.RenderTransform>
<TransformGroup>
<ScaleTransform />
<SkewTransform />
<RotateTransform />
<TranslateTransform />
</TransformGroup>
</Rectangle.RenderTransform>
</Rectangle>
</Grid>
<VisualStateManager.VisualStateGroups>
<VisualStateGroup x:Name="CommonStates">
<VisualState x:Name="Determinate" />
<VisualState x:Name="Indeterminate">
<Storyboard RepeatBehavior="Forever">
<DoubleAnimationUsingKeyFrames Storyboard.TargetName="Animation"
Storyboard.TargetProperty="(UIElement.RenderTransform).(TransformGroup.Children)[0].(ScaleTransform.ScaleX)">
<EasingDoubleKeyFrame KeyTime="0" Value="0.25" />
<EasingDoubleKeyFrame KeyTime="0:0:1" Value="0.25" />
<EasingDoubleKeyFrame KeyTime="0:0:2" Value="0.25" />
</DoubleAnimationUsingKeyFrames>
<PointAnimationUsingKeyFrames Storyboard.TargetName="Animation"
Storyboard.TargetProperty="(UIElement.RenderTransformOrigin)">
<EasingPointKeyFrame KeyTime="0" Value="-0.5,0.5" />
<EasingPointKeyFrame KeyTime="0:0:1" Value="0.5,0.5" />
<EasingPointKeyFrame KeyTime="0:0:2" Value="1.5,0.5" />
</PointAnimationUsingKeyFrames>
</Storyboard>
</VisualState>
</VisualStateGroup>
</VisualStateManager.VisualStateGroups>
</Grid>
<ControlTemplate.Triggers>
<Trigger Property="materialDesign:TransitionAssist.DisableTransitions" Value="True">
<Trigger.EnterActions>
<BeginStoryboard Name="BeginStoryboardOnLoadedNoAnimation"
Storyboard="{StaticResource OnLoadedNoAnimation}" />
</Trigger.EnterActions>
<Trigger.ExitActions>
<RemoveStoryboard BeginStoryboardName="BeginStoryboardOnLoadedNoAnimation" />
</Trigger.ExitActions>
</Trigger>
<MultiTrigger>
<MultiTrigger.Conditions>
<Condition Property="IsVisible" Value="True" />
<Condition Property="materialDesign:TransitionAssist.DisableTransitions"
Value="False" />
</MultiTrigger.Conditions>
<MultiTrigger.EnterActions>
<BeginStoryboard Name="BeginStoryboardOnLoaded"
Storyboard="{StaticResource OnLoaded}" />
</MultiTrigger.EnterActions>
<MultiTrigger.ExitActions>
<RemoveStoryboard BeginStoryboardName="BeginStoryboardOnLoaded" />
</MultiTrigger.ExitActions>
</MultiTrigger>
<Trigger Property="Orientation" Value="Vertical">
<Setter Property="LayoutTransform">
<Setter.Value>
<RotateTransform Angle="-90" />
</Setter.Value>
</Setter>
</Trigger>
<Trigger Property="IsIndeterminate" Value="true">
<Setter TargetName="Indicator" Property="Visibility" Value="Collapsed" />
</Trigger>
</ControlTemplate.Triggers>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
<Style x:Key="ScrollThumbs" TargetType="{x:Type Thumb}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type Thumb}">
<Grid x:Name="Grid">
<Rectangle Width="Auto"
Height="Auto"
HorizontalAlignment="Stretch"
VerticalAlignment="Stretch"
Fill="Transparent" />
<Border x:Name="Rectangle1"
Width="Auto"
Height="Auto"
HorizontalAlignment="Stretch"
VerticalAlignment="Stretch"
Background="{TemplateBinding Background}"
CornerRadius="5" />
</Grid>
<ControlTemplate.Triggers>
<Trigger Property="Tag" Value="Horizontal">
<Setter TargetName="Rectangle1" Property="Width" Value="Auto" />
<Setter TargetName="Rectangle1" Property="Height" Value="7" />
</Trigger>
</ControlTemplate.Triggers>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
<Style x:Key="{x:Type ScrollBar}" TargetType="{x:Type ScrollBar}">
<Setter Property="Stylus.IsFlicksEnabled" Value="false" />
<Setter Property="Foreground" Value="#8C8C8C" />
<Setter Property="Background" Value="Transparent" />
<Setter Property="Width" Value="8" />
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type ScrollBar}">
<Grid x:Name="GridRoot"
Width="8"
Background="{TemplateBinding Background}">
<Grid.RowDefinitions>
<RowDefinition Height="0.00001*" />
</Grid.RowDefinitions>
<Track x:Name="PART_Track"
Grid.Row="0"
Focusable="false"
IsDirectionReversed="true">
<Track.Thumb>
<Thumb x:Name="Thumb"
Background="{TemplateBinding Foreground}"
Style="{DynamicResource ScrollThumbs}" />
</Track.Thumb>
<Track.IncreaseRepeatButton>
<RepeatButton x:Name="PageUp"
Command="ScrollBar.PageDownCommand"
Focusable="false"
Opacity="0" />
</Track.IncreaseRepeatButton>
<Track.DecreaseRepeatButton>
<RepeatButton x:Name="PageDown"
Command="ScrollBar.PageUpCommand"
Focusable="false"
Opacity="0" />
</Track.DecreaseRepeatButton>
</Track>
</Grid>
<ControlTemplate.Triggers>
<Trigger SourceName="Thumb" Property="IsMouseOver" Value="true">
<Setter TargetName="Thumb" Property="Background"
Value="{DynamicResource ButtonSelectBrush}" />
</Trigger>
<Trigger SourceName="Thumb" Property="IsDragging" Value="true">
<Setter TargetName="Thumb" Property="Background"
Value="{DynamicResource DarkBrush}" />
</Trigger>
<Trigger Property="IsEnabled" Value="false">
<Setter TargetName="Thumb" Property="Visibility" Value="Collapsed" />
</Trigger>
<Trigger Property="Orientation" Value="Horizontal">
<Setter TargetName="GridRoot" Property="LayoutTransform">
<Setter.Value>
<RotateTransform Angle="-90" />
</Setter.Value>
</Setter>
<Setter TargetName="PART_Track" Property="LayoutTransform">
<Setter.Value>
<RotateTransform Angle="-90" />
</Setter.Value>
</Setter>
<Setter Property="Width" Value="Auto" />
<Setter Property="Height" Value="8" />
<Setter TargetName="Thumb" Property="Tag" Value="Horizontal" />
<Setter TargetName="PageDown" Property="Command" Value="ScrollBar.PageLeftCommand" />
<Setter TargetName="PageUp" Property="Command" Value="ScrollBar.PageRightCommand" />
</Trigger>
</ControlTemplate.Triggers>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
<Style x:Key="ReleaseImageStyle" TargetType="Image">
<Style.Triggers>
<Trigger Property="IsVisible" Value="False">
<Setter Property="Source" Value="{x:Null}" />
</Trigger>
</Style.Triggers>
</Style>
</ResourceDictionary>
</Application.Resources>
</Application>

View File

@ -1,281 +0,0 @@
#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 <https://www.gnu.org/licenses/>.
#endregion
#if RELEASE
using Pixeval.Objects.Exceptions.Logger;
#endif
using System.Linq;
using System;
using System.Diagnostics;
using System.IO;
using System.IO.Compression;
using System.Net.NetworkInformation;
using System.Security.Cryptography.X509Certificates;
using System.Text;
using System.Threading.Tasks;
using System.Windows;
using CefSharp;
using CefSharp.Wpf;
using Microsoft.Win32;
using Pixeval.Core;
using Pixeval.Objects.I18n;
using Pixeval.Objects.Primitive;
using Pixeval.Persisting;
using Pixeval.Persisting.WebApi;
namespace Pixeval
{
public partial class App
{
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
ExceptionDumper.WriteException(e);
#elif DEBUG
throw e;
#endif
}
protected override async void OnStartup(StartupEventArgs e)
{
// These initializations ensure Pixeval to run properly, they MUST be initialized before everything start
WriteToCurrentUserPathVariable();
InitializeFolders();
CheckCppRedistributable();
CheckMultipleProcess();
await InstallFakeCaCertificate();
// These initializations are for WEB API LOGIN
PortScan(out var proxy, out var pac);
AppContext.ProxyPort = proxy;
AppContext.PacPort = pac;
await WritePac();
CefSharpInitialize();
// These initializations handle USER SESSION AND PIXEVAL UPDATE
await RestoreSettings();
await CheckUpdate();
// These initializations are for PROCESS COMMUNICATION AND PLUGGABLE PROTOCOL
await InstallPluggableProtocolHandler();
CreatePluggableProtocolRegistry();
PluggableProtocolListener.StartServer();
if (e.Args.Any()) await PluggableProtocolParser.Parse(e.Args[0]);
base.OnStartup(e);
}
private static void PortScan(out int proxy, out int pac)
{
var unavailablePorts = IPGlobalProperties.GetIPGlobalProperties().GetActiveTcpConnections().Select(t => t.LocalEndPoint.Port).ToArray();
var rd = new Random();
proxy = RandPorts();
pac = RandPorts();
while (Array.BinarySearch(unavailablePorts, proxy) >= 0) proxy = RandPorts();
while (Array.BinarySearch(unavailablePorts, pac) >= 0) pac = RandPorts();
int RandPorts()
{
return rd.Next(3000, 65536);
}
}
private static async Task InstallPluggableProtocolHandler()
{
var files = Directory.GetFiles(AppContext.InterchangeFolder);
if (!(files.Any(f => f[(f.LastIndexOf(@"\", StringComparison.Ordinal) + 1)..] == "Pixeval.Interchange.runtimeconfig.json") && files.Any(f => f[(f.LastIndexOf(@"\", StringComparison.Ordinal) + 1)..] == "Pixeval.Interchange.exe") && files.Any(f => f[(f.LastIndexOf(@"\", StringComparison.Ordinal) + 1)..] == "Pixeval.Interchange.dll")))
{
foreach (var file in files) File.Delete(file);
if (GetResourceStream(new Uri("pack://application:,,,/Pixeval;component/Resources/interchange.zip")) is { } streamResourceInfo)
{
var interchange = Path.Combine(AppContext.InterchangeFolder, "interchange.zip");
await using (var fs = new FileStream(interchange, FileMode.OpenOrCreate, FileAccess.ReadWrite, FileShare.None))
{
await streamResourceInfo.Stream.CopyToAsync(fs);
}
ZipFile.ExtractToDirectory(interchange, AppContext.InterchangeFolder, true);
File.Delete(Path.Combine(AppContext.InterchangeFolder, "interchange.zip"));
}
}
}
private static void WriteToCurrentUserPathVariable()
{
var pathOld = Environment.GetEnvironmentVariable("PATH", EnvironmentVariableTarget.User);
if (pathOld == null)
{
Environment.SetEnvironmentVariable("PATH", "", EnvironmentVariableTarget.User);
pathOld = "";
}
var paths = pathOld.Split(';').ToList();
var location = Path.GetDirectoryName(typeof(App).Assembly.Location);
if (paths.All(p => p != location))
{
paths.RemoveAll(p => p.ToLower().Contains("pixeval"));
var newPath = paths.Join(';') + ';' + location;
Environment.SetEnvironmentVariable("PATH", newPath, EnvironmentVariableTarget.User);
}
}
private static void InitializeFolders()
{
Directory.CreateDirectory(AppContext.ProjectFolder);
Directory.CreateDirectory(AppContext.SettingsFolder);
Directory.CreateDirectory(AppContext.ExceptionReportFolder);
Directory.CreateDirectory(AppContext.ResourceFolder);
Directory.CreateDirectory(AppContext.PermanentlyFolder);
Directory.CreateDirectory(AppContext.InterchangeFolder);
}
private static void CreatePluggableProtocolRegistry()
{
var regKey = Registry.CurrentUser.OpenSubKey("SOFTWARE\\Classes\\pixeval");
if (regKey == null)
{
using var rootKey = Registry.CurrentUser.CreateSubKey("SOFTWARE\\Classes\\pixeval");
if (rootKey == null) throw new InvalidOperationException(nameof(rootKey));
rootKey.SetValue("", "URL:Pixeval Protocol");
rootKey.SetValue("URL Protocol", "");
using var shellKey = rootKey.CreateSubKey("shell\\open\\command");
if (shellKey == null) throw new InvalidOperationException(nameof(shellKey));
shellKey.SetValue("", $"{Path.Combine(AppContext.InterchangeFolder, "Pixeval.Interchange.exe")} %1");
}
regKey?.Dispose();
}
private static void CheckMultipleProcess()
{
if (Process.GetProcessesByName(AppContext.AppIdentifier).Length > 1)
{
MessageBox.Show(AkaI18N.MultiplePixevalInstanceDetected, AkaI18N.MultiplePixevalInstanceDetectedTitle, MessageBoxButton.OK, MessageBoxImage.Error);
Environment.Exit(-1);
}
}
private static void CheckCppRedistributable()
{
if (!CppRedistributableInstalled())
{
MessageBox.Show(AkaI18N.CppRedistributableRequired, AkaI18N.CppRedistributableRequiredTitle, MessageBoxButton.OK, MessageBoxImage.Exclamation);
Clipboard.SetDataObject("https://support.microsoft.com/zh-cn/help/2977003/the-latest-supported-visual-c-downloads");
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);
}
}
/// <summary>
/// Check if the required Visual C++ Redistributable is installed on the computer
/// </summary>
/// <returns>Cpp redistributable is installed</returns>
private static bool CppRedistributableInstalled()
{
using var key = Registry.LocalMachine.OpenSubKey(@"SOFTWARE\Wow6432Node\Microsoft\VisualStudio\14.0\VC\Runtimes\x64");
if (key == null) return false;
var success = int.TryParse(key.GetValue("Bld").ToString(), out var version);
// visual C++ redistributable Bld table:
// version v2015 v2017 v2019
// ----------------------------------
// Bid 23026 26020 27820
const int vc2015Bld = 23026;
return success && version >= vc2015Bld;
}
private static void CefSharpInitialize()
{
Cef.Initialize(new CefSettings {CefCommandLineArgs = {{"proxy-pac-url", $"http://127.0.0.1:{AppContext.PacPort}/pixeval_pac.pac"}}}, true, browserProcessHandler: null);
}
private static async Task InstallFakeCaCertificate()
{
var certificateManager = new CertificateManager(await CertificateManager.GetFakeCaRootCertificate());
if (!certificateManager.Query(StoreName.Root, StoreLocation.CurrentUser))
{
if (MessageBox.Show(AkaI18N.CertificateInstallationIsRequired, AkaI18N.CertificateInstallationIsRequiredTitle, MessageBoxButton.YesNo, MessageBoxImage.Warning) == MessageBoxResult.Yes)
certificateManager.Install(StoreName.Root, StoreLocation.CurrentUser);
else
Environment.Exit(-1);
}
}
private static async Task RestoreSettings()
{
await Settings.Restore();
BrowsingHistoryAccessor.GlobalLifeTimeScope = new BrowsingHistoryAccessor(200, AppContext.BrowseHistoryDatabase);
}
/// <summary>
/// Write Proxy-Auto-Configuration file to ..\{Directory to Pixeval.dll}\Resource\pixeval_pac.pac,
/// this method is for login usage only, USE AT YOUR OWN RISK
/// </summary>
private static async Task WritePac()
{
var scriptBuilder = new StringBuilder();
scriptBuilder.AppendLine("function FindProxyForURL(url, host) {");
scriptBuilder.AppendLine(" if (shExpMatch(host, \"*.pixiv.net\")) {");
scriptBuilder.AppendLine($" return 'PROXY 127.0.0.1:{AppContext.ProxyPort}';");
scriptBuilder.AppendLine(" }");
scriptBuilder.AppendLine(" return \"DIRECT\";");
scriptBuilder.AppendLine("}");
await File.WriteAllTextAsync(Path.Combine(AppContext.ResourceFolder, "pixeval_pac.pac"), scriptBuilder.ToString());
}
protected override async void OnExit(ExitEventArgs e)
{
CertificateManager.GetFakeCaRootCertificate().Dispose();
CertificateManager.GetFakeServerCertificate().Dispose();
await Settings.Global.Store();
if (Session.Current != null && !Session.Current.AccessToken.IsNullOrEmpty() && !Session.Current.PhpSessionId.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);
}
}
}

View File

@ -1,74 +0,0 @@
#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 <https://www.gnu.org/licenses/>.
#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 = "2.2.5";
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 InterchangeFolder = Path.Combine(ProjectFolder, "interchange");
public static readonly string CacheFolder = Path.Combine(ProjectFolder, "cache");
public static readonly string ResourceFolder = Path.Combine(Path.GetDirectoryName(typeof(App).Assembly.Location)!, "Resource");
public static readonly string PermanentlyFolder = Path.Combine(Path.GetDirectoryName(typeof(App).Assembly.Location)!, "Permanent");
public static readonly string BrowseHistoryDatabase = Path.Combine(ProjectFolder, "history.db");
public static readonly ObservableCollection<TrendingTag> TrendingTags = new ObservableCollection<TrendingTag>();
public static readonly IQualifier<Illustration, IllustrationQualification> 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<bool> UpdateAvailable()
{
const string url = "http://47.95.218.243/Pixeval/version.txt";
var httpClient = new HttpClient();
return await httpClient.GetStringAsync(url) != CurrentVersion;
}
}
}

View File

@ -1,64 +0,0 @@
#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 <https://www.gnu.org/licenses/>.
#endregion
using System.Collections.Generic;
using System.Threading;
namespace Pixeval.Core
{
/// <summary>
/// Abstract implementation of <see cref="IPixivAsyncEnumerable{T}" />, provides the default implementation of cancel a
/// running <see cref="IPixivAsyncEnumerable{T}" /> instance
/// </summary>
/// <typeparam name="T"></typeparam>
public abstract class AbstractPixivAsyncEnumerable<T> : IPixivAsyncEnumerable<T>
{
protected bool IsCancelled { get; private set; }
public abstract int RequestedPages { get; protected set; }
public abstract IAsyncEnumerator<T> GetAsyncEnumerator(CancellationToken cancellationToken = default);
public virtual bool VerifyRationality(T item, IList<T> collection)
{
return item != null && !collection.Contains(item);
}
public virtual void InsertionPolicy(T item, IList<T> collection)
{
if (item != null) collection.Add(item);
}
public void Cancel()
{
IsCancelled = true;
}
public bool IsCancellationRequested()
{
return IsCancelled;
}
public void ReportRequestedPages()
{
RequestedPages++;
}
}
}

View File

@ -1,50 +0,0 @@
#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 <https://www.gnu.org/licenses/>.
#endregion
using System.Collections.Generic;
using System.Threading.Tasks;
namespace Pixeval.Core
{
/// <summary>
/// Provide a set of functions that support iterate an <see cref="IPixivAsyncEnumerable{T}" />
/// </summary>
/// <typeparam name="T">the correspond data type</typeparam>
public abstract class AbstractPixivAsyncEnumerator<T> : IAsyncEnumerator<T>
{
protected IPixivAsyncEnumerable<T> Enumerable;
protected AbstractPixivAsyncEnumerator(IPixivAsyncEnumerable<T> enumerable)
{
Enumerable = enumerable;
}
public ValueTask DisposeAsync()
{
return default;
}
public abstract ValueTask<bool> MoveNextAsync();
public abstract T Current { get; }
protected abstract void UpdateEnumerator();
}
}

View File

@ -1,128 +0,0 @@
#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 <https://www.gnu.org/licenses/>.
#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
{
/// <summary>
/// Manages the global browsing history which lives inside application
/// the underlying implementation is sqlite database
/// </summary>
public class BrowsingHistoryAccessor : ITimelineService, IDisposable
{
public static BrowsingHistoryAccessor GlobalLifeTimeScope;
// current browsing histories
private readonly ObservableCollection<BrowsingHistory> _delegation;
private readonly string _path;
private readonly SQLiteConnection _sqLiteConnection;
private readonly int _stackLimit;
private bool _writable;
public BrowsingHistoryAccessor(int stackLimit, string path)
{
_stackLimit = stackLimit;
_path = path;
_sqLiteConnection = new SQLiteConnection(path, SQLiteOpenFlags.Create | SQLiteOpenFlags.ReadWrite);
_sqLiteConnection.CreateTable<BrowsingHistory>();
_delegation = new ObservableCollection<BrowsingHistory>(_sqLiteConnection.Table<BrowsingHistory>());
}
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);
}
/// <summary>
/// If the database file is not present, we will urgently create one and
/// write all the browsing histories that we have into it
/// </summary>
public void EmergencyRewrite()
{
if (File.Exists(_path)) throw new InvalidOperationException();
using var sql = new SQLiteConnection(_path);
sql.CreateTable<BrowsingHistory>();
sql.InsertAll(Get());
}
/// <summary>
/// Returns currently maintained browsing histories
/// </summary>
/// <returns></returns>
public IEnumerable<BrowsingHistory> Get()
{
return _delegation;
}
/// <summary>
/// Rewrite the local database, you muse call <see cref="SetWritable" />
/// before call this method
/// </summary>
public void Rewrite()
{
if (!_writable) throw new InvalidOperationException();
_sqLiteConnection.DropTable<BrowsingHistory>();
_sqLiteConnection.CreateTable<BrowsingHistory>();
_sqLiteConnection.InsertAll(_delegation);
}
/// <summary>
/// Delete the local database
/// </summary>
public void DropDb()
{
if (File.Exists(_path)) File.Delete(_path);
}
public void SetWritable()
{
_writable = true;
}
}
}

View File

@ -1,51 +0,0 @@
#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 <https://www.gnu.org/licenses/>.
#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);
}
}
}

View File

@ -1,44 +0,0 @@
#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 <https://www.gnu.org/licenses/>.
#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);
}
}
}

View File

@ -1,44 +0,0 @@
#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 <https://www.gnu.org/licenses/>.
#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";
}
}
}

View File

@ -1,47 +0,0 @@
#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 <https://www.gnu.org/licenses/>.
#endregion
using System;
namespace Pixeval.Core
{
public class EnumeratingSchedule
{
private static object _currentItr;
public static void StartNewInstance<T>(IPixivAsyncEnumerable<T> itr)
{
CancelCurrent();
_currentItr = itr;
}
public static IPixivAsyncEnumerable<T> GetCurrentEnumerator<T>()
{
return _currentItr as IPixivAsyncEnumerable<T>;
}
public static void CancelCurrent()
{
var iterator = _currentItr as ICancellable;
iterator?.Cancel();
GC.Collect();
}
}
}

View File

@ -1,157 +0,0 @@
#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 <https://www.gnu.org/licenses/>.
#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<Illustration>
{
protected abstract string Uid { get; }
protected abstract RestrictPolicy RestrictPolicy { get; }
public override int RequestedPages { get; protected set; }
public override bool VerifyRationality(Illustration item, IList<Illustration> collection)
{
return item != null && collection.All(t => t.Id != item.Id) && PixivHelper.VerifyIllustRational(Settings.Global.ExcludeTag, Settings.Global.IncludeTag, Settings.Global.MinBookmark, item);
}
public override IAsyncEnumerator<Illustration> 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<Illustration>
{
private readonly RestrictPolicy _restrictPolicy;
private readonly string _uid;
private GalleryResponse _entity;
private IEnumerator<Illustration> _illustrationsEnumerator;
public GalleryAsyncEnumerator(string uid, IPixivAsyncEnumerable<Illustration> outerInstance, RestrictPolicy restrictPolicy) : base(outerInstance)
{
_uid = uid;
_restrictPolicy = restrictPolicy;
}
public override Illustration Current => _illustrationsEnumerator.Current;
protected override void UpdateEnumerator()
{
_illustrationsEnumerator = _entity.Illusts.NonNull().Select(_ => _.Parse()).GetEnumerator();
}
public override async ValueTask<bool> 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<HttpResponse<GalleryResponse>> TryGetResponse(string url)
{
var result = (await HttpClientFactory.AppApiHttpClient().Apply(h => h.DefaultRequestHeaders.TryAddWithoutValidation("Accept-Language", AkaI18N.GetCultureAcceptLanguage())).GetStringAsync(url)).FromJson<GalleryResponse>();
if (result is { } response && !response.Illusts.IsNullOrEmpty()) return HttpResponse<GalleryResponse>.Wrap(true, response);
return HttpResponse<GalleryResponse>.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;
}
}

View File

@ -1,29 +0,0 @@
#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 <https://www.gnu.org/licenses/>.
#endregion
namespace Pixeval.Core
{
public interface ICancellable
{
void Cancel();
bool IsCancellationRequested();
}
}

View File

@ -1,49 +0,0 @@
#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 <https://www.gnu.org/licenses/>.
#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; }
}
}

View File

@ -1,56 +0,0 @@
#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 <https://www.gnu.org/licenses/>.
#endregion
using System.Collections.Generic;
namespace Pixeval.Core
{
/// <summary>
/// A base class provides several functions to browse the Pixiv content
/// </summary>
/// <typeparam name="T">the correspond data type</typeparam>
public interface IPixivAsyncEnumerable<T> : IAsyncEnumerable<T>, ICancellable
{
/// <summary>
/// Indicates how many pages have been requested
/// </summary>
int RequestedPages { get; }
/// <summary>
/// Basically, this method SHOULD increase the value of <see cref="RequestedPages" />
/// </summary>
void ReportRequestedPages();
/// <summary>
/// Tell the <see cref="IPixivAsyncEnumerable{T}" /> how to insert <see cref="item" /> to a <see cref="IList{T}" />
/// </summary>
/// <param name="item"></param>
/// <param name="collection"></param>
void InsertionPolicy(T item, IList<T> collection);
/// <summary>
/// Check if the <see cref="item" /> has the rationality to be inserted to the <see cref="IList{T}" />
/// </summary>
/// <param name="item"></param>
/// <param name="collection"></param>
/// <returns></returns>
bool VerifyRationality(T item, IList<T> collection);
}
}

View File

@ -1,27 +0,0 @@
#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 <https://www.gnu.org/licenses/>.
#endregion
namespace Pixeval.Core
{
public interface IQualifier<in T, in P>
{
public bool Qualified(T condition, P pattern);
}
}

View File

@ -1,43 +0,0 @@
#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 <https://www.gnu.org/licenses/>.
#endregion
using Pixeval.Data.ViewModel;
namespace Pixeval.Core
{
/// <summary>
/// Provides a set of functions that support a Browsing Timeline
/// </summary>
public interface ITimelineService
{
/// <summary>
/// Check if the <see cref="BrowsingHistory" /> has the rationality to be insert to timeline
/// </summary>
/// <param name="browsingHistory"></param>
/// <returns></returns>
bool VerifyRationality(BrowsingHistory browsingHistory);
/// <summary>
/// Insert a <see cref="BrowsingHistory" /> to timeline
/// </summary>
/// <param name="browsingHistory"></param>
void Insert(BrowsingHistory browsingHistory);
}
}

View File

@ -1,53 +0,0 @@
#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 <https://www.gnu.org/licenses/>.
#endregion
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)
{
return new IllustrationQualification(input switch
{
_ when input.IsNumber() => ConditionType.Id,
_ when input.StartsWith("!") => ConditionType.ExcludeTag,
_ when !input.IsNullOrEmpty() => ConditionType.Tag,
_ => ConditionType.None
}, input);
}
}
public enum ConditionType
{
Id, Tag, ExcludeTag, None
}
}

View File

@ -1,39 +0,0 @@
#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 <https://www.gnu.org/licenses/>.
#endregion
using System.Linq;
using Pixeval.Data.ViewModel;
namespace Pixeval.Core
{
public class IllustrationQualifier : IQualifier<Illustration, IllustrationQualification>
{
public bool Qualified(Illustration condition, IllustrationQualification pattern)
{
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
};
}
}
}

View File

@ -1,95 +0,0 @@
#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 <https://www.gnu.org/licenses/>.
#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<DownloadableIllustration> Downloading = new ObservableCollection<DownloadableIllustration>();
public static readonly ObservableCollection<DownloadableIllustration> Downloaded = new ObservableCollection<DownloadableIllustration>();
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<string> _searchingHistory = new ObservableCollection<string>();
public static void EnqueueSearchHistory(string keyword)
{
if (_searchingHistory.Count == 4) _searchingHistory.RemoveAt(_searchingHistory.Count - 1);
_searchingHistory.Insert(0, keyword);
}
public static IEnumerable<string> GetSearchingHistory()
{
return _searchingHistory;
}
}
}

View File

@ -1,43 +0,0 @@
#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 <https://www.gnu.org/licenses/>.
#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;
}
}
}
}

View File

@ -1,94 +0,0 @@
#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 <https://www.gnu.org/licenses/>.
#endregion
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<IEnumerable<string>> 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/(?<Id>\\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<List<TrendingTag>> GetTrendingTags(this PixivClient _)
{
var result = await HttpClientFactory.AppApiService().GetTrendingTags();
var list = new List<TrendingTag>();
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;
}
public static async ValueTask<bool> 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;
}
}
}
}

View File

@ -1,129 +0,0 @@
#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 <https://www.gnu.org/licenses/>.
#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.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<Illustration> 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<T>(IPixivAsyncEnumerable<T> pixivIterator, IList<T> container, int limit = -1)
{
EnumeratingSchedule.StartNewInstance(pixivIterator);
var enumerator = EnumeratingSchedule.GetCurrentEnumerator<T>();
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)) 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
>= /* Windows 10 April 2018 Update(a.k.a. Redstone 4), which is the version that release Windows Timeline feature */
new Version(10, 0, 17134);
}
}
public static bool VerifyIllustRational(ISet<string> excludeTag, ISet<string> 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;
}
}
}

View File

@ -1,98 +0,0 @@
#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 <https://www.gnu.org/licenses/>.
#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<byte[]> GetBytes(string url)
{
var client = HttpClientFactory.PixivImage();
byte[] res;
try
{
res = await client.GetByteArrayAsync(url);
}
catch
{
return null;
}
return res;
}
public static async Task<BitmapImage> FromUrl(string url)
{
return await FromByteArray(await GetBytes(url));
}
public static async Task<BitmapImage> 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<string> GetResizedBase64UriOfImageFromUrl(string url, string type = null)
{
return $"data:image/{type ?? url[(url.LastIndexOf('.') + 1)..]};base64,{Convert.ToBase64String(await GetBytes(url))}";
}
public static async Task<MemoryStream> Download(string url, IProgress<double> 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<byte>.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<byte>.Shared.Return(byteBuffer, true);
return memoryStream;
}
}
}

View File

@ -1,50 +0,0 @@
#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 <https://www.gnu.org/licenses/>.
#endregion
using System.Threading.Tasks;
using EmbedIO;
using EmbedIO.Routing;
using EmbedIO.WebApi;
using Pixeval.Objects.Primitive;
using Pixeval.UI;
namespace Pixeval.Core
{
public class TransferController : WebApiController
{
[Route(HttpVerbs.Post, "/open")]
public async Task DoOpen()
{
MainWindow.Instance.Dispatcher.Invoke(() => MainWindow.Instance.GlobalActivate());
await PluggableProtocolParser.Parse(await HttpContext.GetRequestBodyAsStringAsync());
}
}
public class PluggableProtocolListener
{
private static WebServer _server;
public static void StartServer()
{
_server = new WebServer(o => o.WithUrlPrefix("http://127.0.0.1:12547").WithMode(HttpListenerMode.Microsoft)).WithWebApi("/", m => m.WithController<TransferController>());
_server.Start();
}
}
}

View File

@ -1,105 +0,0 @@
#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 <https://www.gnu.org/licenses/>.
#endregion
using System.Globalization;
using System.Linq;
using System.Net;
using System.Net.Http;
using System.Text.RegularExpressions;
using System.Threading.Tasks;
using AngleSharp.Html.Parser;
using MahApps.Metro.Controls;
using Pixeval.Data.ViewModel;
using Pixeval.Data.Web.Delegation;
using Pixeval.Objects.Generic;
using Pixeval.UI;
using Refit;
namespace Pixeval.Core
{
public class PluggableProtocolParser
{
private const string IllustRegex = "pixeval://(www\\.)?pixiv\\.net/artworks/(?<id>\\d+)";
private const string UserRegex = "pixeval://(www\\.)?pixiv\\.net/users/(?<id>\\d+)";
private const string SpotlightRegex = "pixeval://(www\\.)?pixivision\\.net/\\w{2}/a/(?<id>\\d+)";
public static async Task Parse(string url)
{
Match match;
if ((match = Regex.Match(url, IllustRegex)).Success)
{
var i = MainWindow.Instance;
await i.Invoke(async () =>
{
if (i.IllustBrowserDialogHost.IsOpen) i.IllustBrowserDialogHost.CurrentSession.Close();
i.OpenIllustBrowser(await PixivHelper.IllustrationInfo(match.Groups["id"].Value), false);
});
}
else if ((match = Regex.Match(url, UserRegex)).Success)
{
var i = MainWindow.Instance;
i.Invoke(() =>
{
i.SetUserBrowserContext(new User {Id = match.Groups["id"].Value});
i.OpenUserBrowser();
});
}
else if ((match = Regex.Match(url, SpotlightRegex)).Success)
{
var articleId = match.Groups["id"].Value;
string title;
// if the current culture is set to en-XX, then we will simply analyze the html and get the title
if (CultureInfo.CurrentCulture.Name.ToLower().StartsWith("en"))
title = await TryGetSpotlightEnTitle(articleId);
else // otherwise, we will try to access the spotlight web API, and analyze html if failed
try
{
title = (await HttpClientFactory.WebApiService().GetSpotlightArticles(articleId)).BodyList[0].Title;
}
catch (ApiException e)
{
if (e.StatusCode == HttpStatusCode.NotFound)
title = await TryGetSpotlightEnTitle(articleId);
else
throw;
}
var tasks = await Tasks<string, Illustration>.Of(await PixivClient.Instance.GetArticleWorks(articleId)).Mapping(PixivHelper.IllustrationInfo).Construct().WhenAll();
var result = tasks.Peek(i =>
{
i.IsManga = true;
i.FromSpotlight = true;
i.SpotlightTitle = title;
}).ToArray();
MainWindow.Instance.Invoke(() => MainWindow.Instance.OpenIllustBrowser(result[0].Apply(r => r.MangaMetadata = result.ToArray()), false));
static async Task<string> TryGetSpotlightEnTitle(string id)
{
var httpClient = new HttpClient();
var html = await httpClient.GetStringAsync($"https://www.pixivision.net/en/a/{id}");
var doc = await new HtmlParser().ParseDocumentAsync(html);
return doc.QuerySelectorAll(".am__title")[0].InnerHtml;
}
}
}
}
}

View File

@ -1,169 +0,0 @@
#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 <https://www.gnu.org/licenses/>.
#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<Illustration>
{
private readonly SearchTagMatchOption _matchOption;
private readonly int _start;
private readonly string _tag;
protected readonly bool IsPremium;
protected AbstractQueryAsyncEnumerable(string tag, SearchTagMatchOption matchOption, bool isPremium, int start = 1)
{
_start = start < 1 ? 1 : start;
_tag = tag;
_matchOption = matchOption;
IsPremium = isPremium;
}
public override int RequestedPages { get; protected set; }
public abstract override void InsertionPolicy(Illustration item, IList<Illustration> collection);
public override bool VerifyRationality(Illustration item, IList<Illustration> collection)
{
return item != null && collection.All(t => t.Id != item.Id) && PixivHelper.VerifyIllustRational(Settings.Global.ExcludeTag, Settings.Global.IncludeTag, Settings.Global.MinBookmark, item);
}
public override IAsyncEnumerator<Illustration> GetAsyncEnumerator(CancellationToken cancellationToken = default)
{
return new QueryAsyncEnumerator(this, _tag, _matchOption, _start, IsPremium);
}
private class QueryAsyncEnumerator : AbstractPixivAsyncEnumerator<Illustration>
{
private readonly int _current;
private readonly bool _isPremium;
private readonly string _keyword;
private readonly SearchTagMatchOption _matchOption;
private QueryWorksResponse _entity;
private IEnumerator<Illustration> _illustrationsEnumerator;
public QueryAsyncEnumerator(IPixivAsyncEnumerable<Illustration> enumerable, string keyword, SearchTagMatchOption matchOption, int current, bool isPremium) : base(enumerable)
{
_keyword = keyword;
_matchOption = matchOption;
_current = current;
_isPremium = isPremium;
}
public override Illustration Current => _illustrationsEnumerator.Current;
protected override void UpdateEnumerator()
{
_illustrationsEnumerator = _entity.Illusts.NonNull().Select(_ => _.Parse()).GetEnumerator();
}
public override async ValueTask<bool> MoveNextAsync()
{
if (_entity == null)
{
if (await TryGetResponse($"/v1/search/illust?search_target={_matchOption.GetEnumAttribute<EnumAlias>().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<HttpResponse<QueryWorksResponse>> TryGetResponse(string url)
{
var res = (await HttpClientFactory.AppApiHttpClient().Apply(h => h.DefaultRequestHeaders.TryAddWithoutValidation("Accept-Language", AkaI18N.GetCultureAcceptLanguage())).GetStringAsync(url)).FromJson<QueryWorksResponse>();
if (res is { } response && !response.Illusts.IsNullOrEmpty()) return HttpResponse<QueryWorksResponse>.Wrap(true, response);
return HttpResponse<QueryWorksResponse>.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<Illustration> 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<Illustration> collection)
{
if (item != null)
{
if (IsPremium)
collection.Add(item);
else
collection.AddSorted(item, IllustrationPopularityComparator.Instance);
}
}
}
}

View File

@ -1,136 +0,0 @@
#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 <https://www.gnu.org/licenses/>.
#endregion
using System;
using Pixeval.Objects.Primitive;
namespace Pixeval.Core
{
[AttributeUsage(AttributeTargets.Field | AttributeTargets.Property)]
public class ForR18Only : Attribute
{
}
public enum RankOption
{
/// <summary>
/// 日榜
/// </summary>
[EnumAlias("day")]
[EnumLocalizedName("RankOptionDay")]
Day,
/// <summary>
/// 周榜
/// </summary>
[EnumAlias("week")]
[EnumLocalizedName("RankOptionWeek")]
Week,
/// <summary>
/// 月榜
/// </summary>
[EnumAlias("month")]
[EnumLocalizedName("RankOptionMonth")]
Month,
/// <summary>
/// 男性向日榜
/// </summary>
[EnumAlias("day_male")]
[EnumLocalizedName("RankOptionDayMale")]
DayMale,
/// <summary>
/// 女性向日榜
/// </summary>
[EnumAlias("day_female")]
[EnumLocalizedName("RankOptionDayFemale")]
DayFemale,
/// <summary>
/// 多图日榜
/// </summary>
[EnumAlias("day_manga")]
[EnumLocalizedName("RankOptionDayManga")]
DayManga,
/// <summary>
/// 多图周榜
/// </summary>
[EnumAlias("week_manga")]
[EnumLocalizedName("RankOptionWeekManga")]
WeekManga,
/// <summary>
/// 原创
/// </summary>
[EnumAlias("week_original")]
[EnumLocalizedName("RankOptionWeekOriginal")]
WeekOriginal,
/// <summary>
/// 新人
/// </summary>
[EnumAlias("week_rookie")]
[EnumLocalizedName("RankOptionWeekRookie")]
WeekRookie,
/// <summary>
/// R18日榜
/// </summary>
[ForR18Only]
[EnumAlias("day_r18")]
[EnumLocalizedName("RankOptionDayR18")]
DayR18,
/// <summary>
/// 男性向R18日榜
/// </summary>
[ForR18Only]
[EnumAlias("day_male_r18")]
[EnumLocalizedName("RankOptionDayMaleR18")]
DayMaleR18,
/// <summary>
/// 女性向R18日榜
/// </summary>
[ForR18Only]
[EnumAlias("day_female_r18")]
[EnumLocalizedName("RankOptionDayFemaleR18")]
DayFemaleR18,
/// <summary>
/// R18周榜
/// </summary>
[ForR18Only]
[EnumAlias("week_r18")]
[EnumLocalizedName("RankOptionWeekR18")]
WeekR18,
/// <summary>
/// R18G周榜
/// </summary>
[ForR18Only]
[EnumAlias("week_r18g")]
[EnumLocalizedName("RankOptionWeekR18G")]
WeekR18G
}
}

View File

@ -1,128 +0,0 @@
#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 <https://www.gnu.org/licenses/>.
#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<Illustration>
{
private readonly DateTime _dateTime;
private readonly RankOption _rankOption;
public RankingAsyncEnumerable(RankOption rankOption, DateTime dateTime)
{
_rankOption = rankOption;
_dateTime = dateTime;
}
public override int RequestedPages { get; protected set; }
public override bool VerifyRationality(Illustration item, IList<Illustration> collection)
{
return item != null && collection.All(t => t.Id != item.Id) && PixivHelper.VerifyIllustRational(Settings.Global.ExcludeTag, Settings.Global.IncludeTag, Settings.Global.MinBookmark, item);
}
public override void InsertionPolicy(Illustration item, IList<Illustration> collection)
{
collection.Add(item);
}
public override IAsyncEnumerator<Illustration> GetAsyncEnumerator(CancellationToken cancellationToken = default)
{
return new RankingAsyncEnumerator(this, _rankOption, _dateTime);
}
private class RankingAsyncEnumerator : AbstractPixivAsyncEnumerator<Illustration>
{
private readonly string _dateTimeParameter;
private readonly string _rankOptionParameter;
private RankingResponse _entity;
private IEnumerator<Illustration> _illustrationEnumerator;
public RankingAsyncEnumerator(IPixivAsyncEnumerable<Illustration> enumerable, RankOption rankOption, DateTime dateTime) : base(enumerable)
{
_rankOptionParameter = rankOption.GetEnumAttribute<EnumAlias>().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<bool> 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<HttpResponse<RankingResponse>> TryGetResponse(string url)
{
var result = (await HttpClientFactory.AppApiHttpClient().Apply(h => h.DefaultRequestHeaders.TryAddWithoutValidation("Accept-Language", AkaI18N.GetCultureAcceptLanguage())).GetStringAsync(url)).FromJson<RankingResponse>();
if (result is { } response && !response.Illusts.IsNullOrEmpty()) return HttpResponse<RankingResponse>.Wrap(true, response);
return HttpResponse<RankingResponse>.Wrap(false);
}
}
}
}

View File

@ -1,127 +0,0 @@
#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 <https://www.gnu.org/licenses/>.
#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<Illustration>
{
public override int RequestedPages { get; protected set; }
public abstract override void InsertionPolicy(Illustration item, IList<Illustration> collection);
public override bool VerifyRationality(Illustration item, IList<Illustration> collection)
{
return item != null && collection.All(t => t.Id != item.Id) && PixivHelper.VerifyIllustRational(Settings.Global.ExcludeTag, Settings.Global.IncludeTag, Settings.Global.MinBookmark, item);
}
public override IAsyncEnumerator<Illustration> GetAsyncEnumerator(CancellationToken cancellationToken = default)
{
return new RecommendAsyncEnumerator(this);
}
private class RecommendAsyncEnumerator : AbstractPixivAsyncEnumerator<Illustration>
{
private RecommendResponse _entity;
private IEnumerator<Illustration> _illustrationEnumerator;
public RecommendAsyncEnumerator(IPixivAsyncEnumerable<Illustration> enumerable) : base(enumerable)
{
}
public override Illustration Current => _illustrationEnumerator.Current;
protected override void UpdateEnumerator()
{
_illustrationEnumerator = _entity.Illusts.NonNull().Select(_ => _.Parse()).GetEnumerator();
}
public override async ValueTask<bool> 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<HttpResponse<RecommendResponse>> TryGetResponse(string url)
{
var res = (await HttpClientFactory.AppApiHttpClient().Apply(h => h.DefaultRequestHeaders.TryAddWithoutValidation("Accept-Language", AkaI18N.GetCultureAcceptLanguage())).GetStringAsync(url)).FromJson<RecommendResponse>();
if (res is { } response && !response.Illusts.IsNullOrEmpty()) return HttpResponse<RecommendResponse>.Wrap(true, response);
return HttpResponse<RecommendResponse>.Wrap(false);
}
}
}
public class PopularityRecommendAsyncEnumerable : AbstractRecommendAsyncEnumerable
{
public override void InsertionPolicy(Illustration item, IList<Illustration> collection)
{
if (item != null) collection.AddSorted(item, IllustrationPopularityComparator.Instance);
}
}
public class PublishDateRecommendAsyncEnumerable : AbstractRecommendAsyncEnumerable
{
public override void InsertionPolicy(Illustration item, IList<Illustration> collection)
{
if (item != null) collection.AddSorted(item, IllustrationPublishDateComparator.Instance);
}
}
}

View File

@ -1,68 +0,0 @@
#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 <https://www.gnu.org/licenses/>.
#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<User> _currentIllustrators = new List<User>();
private int _index;
private int _requestTimes;
public async Task<IEnumerable<User>> 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()));
}
}
}

View File

@ -1,27 +0,0 @@
#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 <https://www.gnu.org/licenses/>.
#endregion
namespace Pixeval.Core
{
public enum RestrictPolicy
{
Public, Private
}
}

View File

@ -1,48 +0,0 @@
#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 <https://www.gnu.org/licenses/>.
#endregion
using Pixeval.Objects.Primitive;
namespace Pixeval.Core
{
public enum SearchTagMatchOption
{
/// <summary>
/// 部分一致
/// </summary>
[EnumAlias("partial_match_for_tags")]
[EnumLocalizedName("TagMatchingPartialMatch")]
PartialMatchForTags,
/// <summary>
/// 完全一致
/// </summary>
[EnumAlias("exact_match_for_tags")]
[EnumLocalizedName("TagMatchingExactMatch")]
ExactMatchForTags,
/// <summary>
/// 标题和说明文
/// </summary>
[EnumAlias("title_and_caption")]
[EnumLocalizedName("TagMatchingTitleAndCaption")]
TitleAndCaption
}
}

View File

@ -1,112 +0,0 @@
#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 <https://www.gnu.org/licenses/>.
#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<SpotlightArticle>
{
private readonly int _start;
public SpotlightQueryAsyncEnumerable(int start)
{
_start = start < 1 ? 1 : start;
}
public override int RequestedPages { get; protected set; }
public override IAsyncEnumerator<SpotlightArticle> GetAsyncEnumerator(CancellationToken cancellationToken = default)
{
return new SpotlightArticleAsyncEnumerator(this, _start);
}
private class SpotlightArticleAsyncEnumerator : AbstractPixivAsyncEnumerator<SpotlightArticle>
{
private int _current;
private SpotlightResponse _entity;
private IEnumerator<SpotlightArticle> _spotlightArticleEnumerator;
public SpotlightArticleAsyncEnumerator(IPixivAsyncEnumerable<SpotlightArticle> enumerable, int current) : base(enumerable)
{
_current = current;
}
public override SpotlightArticle Current => _spotlightArticleEnumerator.Current;
protected override void UpdateEnumerator()
{
_spotlightArticleEnumerator = _entity.SpotlightArticles.NonNull().GetEnumerator();
}
public override async ValueTask<bool> 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<HttpResponse<SpotlightResponse>> TryGetResponse()
{
var res = await HttpClientFactory.AppApiService().GetSpotlights(_current++ * 10);
if (res is { } response && !response.SpotlightArticles.IsNullOrEmpty()) return HttpResponse<SpotlightResponse>.Wrap(true, response);
return HttpResponse<SpotlightResponse>.Wrap(false);
}
}
}
}

View File

@ -1,229 +0,0 @@
#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 <https://www.gnu.org/licenses/>.
#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
{
/// <summary>
/// This class is piece of shit
/// </summary>
public class TrendsAsyncEnumerable : AbstractPixivAsyncEnumerable<Trends>
{
public override int RequestedPages { get; protected set; }
public override IAsyncEnumerator<Trends> GetAsyncEnumerator(CancellationToken cancellationToken = default)
{
return new TrendsAsyncEnumerator(this);
}
private class TrendsAsyncEnumerator : AbstractPixivAsyncEnumerator<Trends>
{
private TrendsRequestContext _requestContext;
private IEnumerator<Trends> _trendsEnumerable;
private string _tt;
public TrendsAsyncEnumerator(IPixivAsyncEnumerable<Trends> enumerable) : base(enumerable)
{
}
public override Trends Current => _trendsEnumerable.Current;
public override async ValueTask<bool> MoveNextAsync()
{
if (_requestContext == null)
{
if (await GetResponse(BuildRequestUrl()) is (true, var result))
{
_tt = Regex.Match(result, "tt: \"(?<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<string>(), UnifyToken = json["param"]?["unify_token"]?.Value<string>(), Sid = json["next_max_sid"]?.Value<long>().ToString(), IsLastPage = json["is_last_page"]?.Value<int>() == 1};
}
private static TrendsRequestContext ExtractRequestParametersFromRawJson(string json)
{
var obj = JObject.Parse(json);
return new TrendsRequestContext {Mode = obj["stacc"]?["param"]?["mode"]?.Value<string>(), UnifyToken = obj["stacc"]?["param"]?["unify_token"]?.Value<string>(), Sid = obj["stacc"]?["next_max_sid"]?.Value<long>().ToString(), IsLastPage = obj["stacc"]?["is_last_page"]?.Value<int>() == 1};
}
private static Task<IEnumerable<Trends>> ParsePreloadJsonFromHtml(string html)
{
return ParsePreloadJson(ExtractPreloadJsonSnippet(html));
}
private static string ExtractPreloadJsonSnippet(string html)
{
var match = Regex.Match(html, "pixiv\\.stacc\\.env\\.preload\\.stacc \\= (?<json>.*);");
if (!match.Success) throw new QueryNotRespondingException();
return match.Groups["json"].Value;
}
private static Task<IEnumerable<Trends>> ParseRawJson(string json)
{
var stacc = JObject.Parse(json)["stacc"]?.ToString();
return ParsePreloadJson(stacc);
}
private static async Task<IEnumerable<Trends>> ParsePreloadJson(string json)
{
var tasks = new List<Task<Trends>>();
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<string>() == timelineProp?["id"]?.Value<string>());
if (statusObj?.First == null) return null;
var statusObjProp = statusObj.First;
var trendsObj = new Trends
{
PostDate = DateTime.Parse(statusObjProp?["post_date"]?.Value<string>()!, CultureInfo.CurrentCulture),
PostUserId = statusObjProp["post_user"]?["id"]?.Value<string>(),
TrendObjectId = statusObjProp["type"]?.Value<string>() switch
{
var type when type == "add_illust" || type == "add_bookmark" => statusObjProp["ref_illust"]?["id"]?.Value<string>(),
"add_favorite" => statusObjProp["ref_user"]?["id"]?.Value<string>(),
_ => null
}
};
var matchingPostUser = user?.FirstOrDefault(uChild => uChild.First?["id"]?.Value<string>() == trendsObj.PostUserId);
if (matchingPostUser != null)
{
trendsObj.PostUserThumbnail = matchingPostUser.First?["profile_image"]?.First?.First?["url"]?["m"]?.Value<string>();
trendsObj.PostUserName = matchingPostUser.First?["name"]?.Value<string>();
}
else
{
return null;
}
trendsObj.Type = statusObjProp["type"]?.Value<string>() 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<string>() == trendsObj.TrendObjectId)?.First?["url"]?["m"]?.Value<string>(),
TrendType.AddFavorite => user.FirstOrDefault(uChild => uChild.First?["id"]?.Value<string>() == trendsObj.TrendObjectId)?.First?["profile_image"]?.First?.First?["url"]?["s"]?.Value<string>(),
(TrendType) (-1) => null,
_ => throw new ArgumentOutOfRangeException()
};
if (trendsObj.Type != TrendType.AddFavorite)
{
var illustration = illust.FirstOrDefault(iChild => iChild.First?["id"]?.Value<string>() == trendsObj.TrendObjectId);
if (illustration != null)
{
trendsObj.ByName = user.FirstOrDefault(uChild => uChild.First?["id"]?.Value<string>() == illustration.First?["post_user"]?["id"]?.Value<string>())?.First["name"].Value<string>();
trendsObj.TrendObjName = illustration.First["title"].Value<string>();
}
}
else
{
trendsObj.TrendObjName = user.FirstOrDefault(uChild => uChild.First?["id"]?.Value<string>() == trendsObj.TrendObjectId)?.First["name"].Value<string>();
trendsObj.IsReferToUser = true;
}
return trendsObj;
});
tasks.Add(task);
}
return await Task.WhenAll(tasks);
}
protected override void UpdateEnumerator()
{
throw new NotImplementedException();
}
private static async Task<HttpResponse<string>> GetResponse(string url)
{
var result = await HttpClientFactory.WebApiHttpClient().Apply(h => h.DefaultRequestHeaders.TryAddWithoutValidation("Accept-Language", AkaI18N.GetCultureAcceptLanguage())).GetStringAsync(url);
return !result.IsNullOrEmpty() ? HttpResponse<string>.Wrap(true, result) : HttpResponse<string>.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; }
}
}
}

View File

@ -1,124 +0,0 @@
#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 <https://www.gnu.org/licenses/>.
#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<Illustration>
{
private readonly string _uid;
public UploadAsyncEnumerable(string uid)
{
_uid = uid;
}
public override int RequestedPages { get; protected set; }
public override void InsertionPolicy(Illustration item, IList<Illustration> collection)
{
if (item != null) collection.AddSorted(item, IllustrationPublishDateComparator.Instance);
}
public override bool VerifyRationality(Illustration item, IList<Illustration> collection)
{
return item != null && collection.All(t => t.Id != item.Id) && PixivHelper.VerifyIllustRational(Settings.Global.ExcludeTag, Settings.Global.IncludeTag, Settings.Global.MinBookmark, item);
}
public override IAsyncEnumerator<Illustration> GetAsyncEnumerator(CancellationToken cancellationToken = default)
{
return new UploadAsyncEnumerator(this, _uid);
}
private class UploadAsyncEnumerator : AbstractPixivAsyncEnumerator<Illustration>
{
private readonly string _uid;
private UploadResponse _entity;
private IEnumerator<Illustration> _illustrationEnumerator;
public UploadAsyncEnumerator(IPixivAsyncEnumerable<Illustration> enumerable, string uid) : base(enumerable)
{
_uid = uid;
}
public override Illustration Current => _illustrationEnumerator.Current;
protected override void UpdateEnumerator()
{
_illustrationEnumerator = _entity.Illusts.NonNull().Select(_ => _.Parse()).GetEnumerator();
}
public override async ValueTask<bool> 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<HttpResponse<UploadResponse>> TryGetResponse(string url)
{
var res = (await HttpClientFactory.AppApiHttpClient().Apply(h => h.DefaultRequestHeaders.TryAddWithoutValidation("Accept-Language", AkaI18N.GetCultureAcceptLanguage())).GetStringAsync(url)).FromJson<UploadResponse>();
if (res is { } response && !response.Illusts.IsNullOrEmpty()) return HttpResponse<UploadResponse>.Wrap(true, response);
return HttpResponse<UploadResponse>.Wrap(false);
}
}
}
}

View File

@ -1,151 +0,0 @@
#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 <https://www.gnu.org/licenses/>.
#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<User>
{
protected abstract string Uid { get; }
protected abstract RestrictPolicy RestrictPolicy { get; }
public override int RequestedPages { get; protected set; }
public override IAsyncEnumerator<User> 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<User>
{
private readonly RestrictPolicy _restrictPolicy;
private readonly string _userId;
private FollowingResponse _entity;
private IEnumerator<User> _followerEnumerator;
public UserFollowingAsyncEnumerator(IPixivAsyncEnumerable<User> enumerable, string userId, RestrictPolicy restrictPolicy) : base(enumerable)
{
_userId = userId;
_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<bool> 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<HttpResponse<FollowingResponse>> TryGetResponse(string url)
{
var res = (await HttpClientFactory.AppApiHttpClient().GetStringAsync(url)).FromJson<FollowingResponse>();
if (res is { } response && !response.UserPreviews.IsNullOrEmpty()) return HttpResponse<FollowingResponse>.Wrap(true, response);
return HttpResponse<FollowingResponse>.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;
}
}

View File

@ -1,111 +0,0 @@
#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 <https://www.gnu.org/licenses/>.
#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<User>
{
private readonly string _keyword;
public UserPreviewAsyncEnumerable(string keyword)
{
_keyword = keyword;
}
public override int RequestedPages { get; protected set; }
public override IAsyncEnumerator<User> GetAsyncEnumerator(CancellationToken cancellationToken = default)
{
return new UserPreviewAsyncEnumerator(this, _keyword);
}
private class UserPreviewAsyncEnumerator : AbstractPixivAsyncEnumerator<User>
{
private readonly string _keyword;
private UserNavResponse _entity;
private IEnumerator<User> _userPreviewEnumerator;
public UserPreviewAsyncEnumerator(IPixivAsyncEnumerable<User> enumerable, string keyword) : base(enumerable)
{
_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<bool> 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<HttpResponse<UserNavResponse>> TryGetResponse(string url)
{
var res = (await HttpClientFactory.AppApiHttpClient().GetStringAsync(url)).FromJson<UserNavResponse>();
if (res is { } response && !response.UserPreviews.IsNullOrEmpty()) return HttpResponse<UserNavResponse>.Wrap(true, response);
return HttpResponse<UserNavResponse>.Wrap(false);
}
}
}
}

View File

@ -1,109 +0,0 @@
#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 <https://www.gnu.org/licenses/>.
#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<Illustration>
{
public override int RequestedPages { get; protected set; }
public override IAsyncEnumerator<Illustration> GetAsyncEnumerator(CancellationToken cancellationToken = default)
{
return new UserUpdateAsyncEnumerator(this);
}
public override bool VerifyRationality(Illustration item, IList<Illustration> collection)
{
return item != null && collection.All(t => t.Id != item.Id) && PixivHelper.VerifyIllustRational(Settings.Global.ExcludeTag, Settings.Global.IncludeTag, Settings.Global.MinBookmark, item);
}
private class UserUpdateAsyncEnumerator : AbstractPixivAsyncEnumerator<Illustration>
{
private UserUpdateResponse _entity;
private IEnumerator<Illustration> _illustrationEnumerator;
public UserUpdateAsyncEnumerator(IPixivAsyncEnumerable<Illustration> enumerable) : base(enumerable)
{
}
public override Illustration Current => _illustrationEnumerator.Current;
protected override void UpdateEnumerator()
{
_illustrationEnumerator = _entity.Illusts.NonNull().Select(_ => _.Parse()).GetEnumerator();
}
public override async ValueTask<bool> 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<HttpResponse<UserUpdateResponse>> TryGetResponse(string url)
{
var res = (await HttpClientFactory.AppApiHttpClient().Apply(h => h.DefaultRequestHeaders.TryAddWithoutValidation("Accept-Language", AkaI18N.GetCultureAcceptLanguage())).GetStringAsync(url)).FromJson<UserUpdateResponse>();
if (res is { } response && !response.Illusts.IsNullOrEmpty()) return HttpResponse<UserUpdateResponse>.Wrap(true, response);
return HttpResponse<UserUpdateResponse>.Wrap(false);
}
}
}
}

View File

@ -1,135 +0,0 @@
#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 <https://www.gnu.org/licenses/>.
#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
{
/// <summary>
/// Provides a set of functions to create a Windows 10 Timeline Activity,
/// for more information and underlying implementation, see
/// <a href="https://docs.microsoft.com/en-us/windows/apps/desktop/modernize/modernize-wpf-tutorial-4" />
/// </summary>
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<PixevalTimelineModel> 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; }
}
}
}

View File

@ -1,27 +0,0 @@
#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 <https://www.gnu.org/licenses/>.
#endregion
namespace Pixeval.Data
{
public interface IParser<out T>
{
T Parse();
}
}

View File

@ -1,32 +0,0 @@
#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 <https://www.gnu.org/licenses/>.
#endregion
using PropertyChanged;
namespace Pixeval.Data.ViewModel
{
[AddINotifyPropertyChangedInterface]
public class AutoCompletion
{
public string Tag { get; set; }
public string TranslatedName { get; set; }
}
}

View File

@ -1,61 +0,0 @@
#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 <https://www.gnu.org/licenses/>.
#endregion
using PropertyChanged;
using SQLite;
namespace Pixeval.Data.ViewModel
{
[AddINotifyPropertyChangedInterface]
public class BrowsingHistory
{
[PrimaryKey]
[AutoIncrement]
public int Index { get; set; }
/// <summary>
/// represents the string form of the <see cref="BrowsingHistory" />, by default, it is the name/title
/// </summary>
public string BrowseObjectState { get; set; }
public string BrowseObjectThumbnail { get; set; }
/// <summary>
/// get the illustrator of the illust which the current BrowsingHistory is representing,
/// this property will be valid only if the <see cref="IsReferToIllust" /> property is set
/// to true
/// </summary>
public string IllustratorName { get; set; }
public bool IsReferToUser { get; set; }
public bool IsReferToIllust { get; set; }
public bool IsReferToSpotlight { get; set; }
/// <summary>
/// represents the string form of the identification of current BrowsingHistory, by default
/// it is illust id/user id/spotlight id
/// </summary>
public string BrowseObjectId { get; set; }
public string Type { get; set; }
}
}

View File

@ -1,33 +0,0 @@
#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 <https://www.gnu.org/licenses/>.
#endregion
using PropertyChanged;
namespace Pixeval.Data.ViewModel
{
[AddINotifyPropertyChangedInterface]
public class ConditionString
{
[DoNotNotify]
public static ConditionString Shared = new ConditionString();
public string Condition { get; set; }
}
}

View File

@ -1,200 +0,0 @@
#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 <https://www.gnu.org/licenses/>.
#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<DownloadStateEnum> DownloadState { get; set; } = new Observable<DownloadStateEnum>(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<double>(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<double>(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
}
}

View File

@ -1,46 +0,0 @@
#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 <https://www.gnu.org/licenses/>.
#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; }
}
}

View File

@ -1,114 +0,0 @@
#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 <https://www.gnu.org/licenses/>.
#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<Tag> 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<Illustration>
{
public static readonly IllustrationPopularityComparator Instance = new IllustrationPopularityComparator();
// reverse-ordered(a.k.a. descend order)
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<Illustration>
{
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;
}
}
}

View File

@ -1,60 +0,0 @@
#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 <https://www.gnu.org/licenses/>.
#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<RankOption>().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<EnumLocalizedName>().Name);
}
public string Name { get; set; }
public bool IsSelected { get; set; }
public RankOption Corresponding { get; set; }
public override string ToString()
{
return Name;
}
}
}

View File

@ -1,50 +0,0 @@
#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 <https://www.gnu.org/licenses/>.
#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<SearchTagMatchOptionModel> AllPossibleMatchOptions = new[] {PartialMatchModel, ExactMatchModel, TitleAndCaptionModel};
public SearchTagMatchOptionModel(SearchTagMatchOption corresponding)
{
Description = AkaI18N.GetResource(corresponding.GetEnumAttribute<EnumLocalizedName>().Name);
Corresponding = corresponding;
}
public string Description { get; set; }
public SearchTagMatchOption Corresponding { get; set; }
}
}

View File

@ -1,69 +0,0 @@
#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 <https://www.gnu.org/licenses/>.
#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<string, Illustration>.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);
}
}
}

View File

@ -1,34 +0,0 @@
#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 <https://www.gnu.org/licenses/>.
#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; }
}
}

View File

@ -1,67 +0,0 @@
#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 <https://www.gnu.org/licenses/>.
#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
{
/// <summary>
/// Bookmark
/// </summary>
AddBookmark,
/// <summary>
/// New illust
/// </summary>
AddIllust,
/// <summary>
/// New follow
/// </summary>
AddFavorite
}
}

View File

@ -1,46 +0,0 @@
#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 <https://www.gnu.org/licenses/>.
#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];
}
}

View File

@ -1,89 +0,0 @@
#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 <https://www.gnu.org/licenses/>.
#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)
{
_requestHandler = requestHandler;
_directConnect = directConnect;
ServerCertificateCustomValidationCallback = DangerousAcceptAnyServerCertificateValidator;
}
protected abstract DnsResolver DnsResolver { get; set; }
protected override async Task<HttpResponseMessage> 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;
}
}
}

View File

@ -1,135 +0,0 @@
#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 <https://www.gnu.org/licenses/>.
#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<Dictionary<string, List<IPAddress>>> DnsCache = new ThreadLocal<Dictionary<string, List<IPAddress>>>(() => new Dictionary<string, List<IPAddress>>());
protected async Task<DnsResolveResponse> GetDnsJson(string hostname)
{
return await RestService.For<IResolveDnsProtocol>(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<IReadOnlyList<IPAddress>> 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<IPAddress>(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<IPAddress> Lookup()
{
return UseDefaultDns().ToImmutableList();
}
// private static void CacheDns(string hostname, IEnumerable<IPAddress> ipList)
// {
// if (DnsCache.Value.ContainsKey(hostname))
// DnsCache.Value[hostname].AddRange(ipList);
// else
// DnsCache.Value[hostname] = new List<IPAddress>(ipList);
// }
protected abstract IEnumerable<IPAddress> UseDefaultDns();
private class IpAddressEqualityComparer : IEqualityComparer<IPAddress>
{
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();
}
}
}
}

View File

@ -1,72 +0,0 @@
#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 <https://www.gnu.org/licenses/>.
#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<IAppApiProtocol>(PixivApi(ProtocolBase.AppApiBaseUrl, Settings.Global.DirectConnect));
}
public static IWebApiProtocol WebApiService()
{
return RestService.For<IWebApiProtocol>(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<HttpResponseMessage> GetResponseHeader(HttpClient client, string uri)
{
return client.GetAsync(uri, HttpCompletionOption.ResponseHeadersRead);
}
}
}

View File

@ -1,29 +0,0 @@
#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 <https://www.gnu.org/licenses/>.
#endregion
using System.Net.Http;
namespace Pixeval.Data.Web.Delegation
{
public interface IHttpRequestHandler
{
void Handle(HttpRequestMessage httpRequestMessage);
}
}

View File

@ -1,37 +0,0 @@
#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 <https://www.gnu.org/licenses/>.
#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<IPAddress> 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");
}
}
}

View File

@ -1,38 +0,0 @@
#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 <https://www.gnu.org/licenses/>.
#endregion
using System.Net.Http;
namespace Pixeval.Data.Web.Delegation
{
public class PixivApiHttpClientHandler : DnsResolvedHttpClientHandler
{
private PixivApiHttpClientHandler(bool directConnect) : base(PixivAuthenticationHttpRequestHandler.Instance, directConnect)
{
}
protected override DnsResolver DnsResolver { get; set; } = PixivApiDnsResolver.Instance;
public static HttpMessageHandler Instance(bool directConnect)
{
return new PixivApiHttpClientHandler(directConnect);
}
}
}

View File

@ -1,62 +0,0 @@
#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 <https://www.gnu.org/licenses/>.
#endregion
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 PixivAuthenticationHttpRequestHandler : IHttpRequestHandler
{
public static readonly PixivAuthenticationHttpRequestHandler Instance = new PixivAuthenticationHttpRequestHandler();
protected PixivAuthenticationHttpRequestHandler()
{
}
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":
if (Session.Current.PhpSessionId.IsNullOrEmpty()) throw new TokenNotFoundException($"{nameof(Session.Current.PhpSessionId)} 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.TryAddWithoutValidation("Cookie", $"PHPSESSID={Session.Current.PhpSessionId}");
break;
}
if (!httpRequestMessage.Headers.Contains("Accept-Language")) httpRequestMessage.Headers.TryAddWithoutValidation("Accept-Language", AkaI18N.GetCultureAcceptLanguage());
}
}
}

View File

@ -1,35 +0,0 @@
#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 <https://www.gnu.org/licenses/>.
#endregion
using System.Net.Http;
namespace Pixeval.Data.Web.Delegation
{
public class PixivImageHttpClientHandler : DnsResolvedHttpClientHandler
{
public static readonly HttpMessageHandler Instance = new PixivImageHttpClientHandler();
private PixivImageHttpClientHandler() : base(PixivAuthenticationHttpRequestHandler.Instance, false)
{
}
protected override DnsResolver DnsResolver { get; set; } = null;
}
}

View File

@ -1,47 +0,0 @@
#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 <https://www.gnu.org/licenses/>.
#endregion
using System;
namespace Pixeval.Data.Web
{
public class HttpResponse<T> : Tuple<bool, T>
{
private HttpResponse(bool status, T response) : base(status, response)
{
}
public static HttpResponse<T> Wrap(bool status)
{
return new HttpResponse<T>(status, default);
}
public static HttpResponse<T> Wrap(bool status, T response)
{
return new HttpResponse<T>(status, response);
}
public void Deconstruct(out bool status, out T response)
{
status = Item1;
response = Item2;
}
}
}

View File

@ -1,64 +0,0 @@
#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 <https://www.gnu.org/licenses/>.
#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<UserInformationResponse> GetUserInformation(UserInformationRequest userInformationRequest);
[Get("/v1/spotlight/articles?category=all")]
Task<SpotlightResponse> 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<UgoiraMetadataResponse> GetUgoiraMetadata([AliasAs("illust_id")] string id);
[Post("/v2/illust/bookmark/add")]
Task AddBookmark([Body(BodySerializationMethod.UrlEncoded)] AddBookmarkRequest addBookmarkRequest);
[Get("/v2/search/autocomplete")]
Task<AutoCompletionResponse> GetAutoCompletion(AutoCompletionRequest autoCompletionRequest);
[Get("/v1/illust/detail")]
Task<SingleWorkResponse> GetSingle([AliasAs("illust_id")] string id);
[Get("/v1/user/recommended?filter=for_android")]
Task<RecommendIllustratorResponse> GetRecommendIllustrators(RecommendIllustratorRequest recommendIllustratorRequest);
[Get("/v1/trending-tags/illust?filter=for_android")]
Task<TrendingTagResponse> GetTrendingTags();
}
}

View File

@ -1,33 +0,0 @@
#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 <https://www.gnu.org/licenses/>.
#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<DnsResolveResponse> ResolveDns(DnsResolveRequest dnsResolverRequest);
}
}

View File

@ -1,35 +0,0 @@
#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 <https://www.gnu.org/licenses/>.
#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<HttpResponseMessage> GetSauce([AliasAs("file")]
StreamPart stream);
}
}

View File

@ -1,37 +0,0 @@
#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 <https://www.gnu.org/licenses/>.
#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<TokenResponse> GetTokenByPassword([Body(BodySerializationMethod.UrlEncoded)] PasswordTokenRequest body, [Header("X-Client-Time")] string clientTime, [Header("X-Client-Hash")] string clientHash);
[Post("/auth/token")]
Task<TokenResponse> RefreshToken([Body(BodySerializationMethod.UrlEncoded)] RefreshTokenRequest body);
}
}

View File

@ -1,41 +0,0 @@
#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 <https://www.gnu.org/licenses/>.
#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<HttpResponseMessage> ToggleR18State([Body(BodySerializationMethod.UrlEncoded)] ToggleR18StateRequest toggleR18StateRequest);
[Get("/ajax/showcase/article")]
Task<SpotlightArticleResponse> GetSpotlightArticles([AliasAs("article_id")] string articleId);
[Get("/touch/ajax/user/details")]
Task<WebApiUserDetailResponse> GetWebApiUserDetail(string id);
}
}

View File

@ -1,37 +0,0 @@
#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 <https://www.gnu.org/licenses/>.
#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";
}
}

View File

@ -1,33 +0,0 @@
#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 <https://www.gnu.org/licenses/>.
#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; }
}
}

View File

@ -1,33 +0,0 @@
#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 <https://www.gnu.org/licenses/>.
#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; }
}
}

View File

@ -1,30 +0,0 @@
#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 <https://www.gnu.org/licenses/>.
#endregion
using Refit;
namespace Pixeval.Data.Web.Request
{
public class DeleteBookmarkRequest
{
[AliasAs("illust_id")]
public string IllustId { get; set; }
}
}

View File

@ -1,42 +0,0 @@
#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 <https://www.gnu.org/licenses/>.
#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; }
}
}

View File

@ -1,33 +0,0 @@
#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 <https://www.gnu.org/licenses/>.
#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";
}
}

View File

@ -1,45 +0,0 @@
#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 <https://www.gnu.org/licenses/>.
#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";
}
}

View File

@ -1,33 +0,0 @@
#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 <https://www.gnu.org/licenses/>.
#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;
}
}

Some files were not shown because too many files have changed in this diff Show More