Skip to content

Commit

Permalink
- changed PixivAPI.Login to new login process according to RFC 7636
Browse files Browse the repository at this point in the history
  • Loading branch information
sentouki committed Sep 23, 2021
1 parent 8753bb0 commit 901b5a8
Show file tree
Hide file tree
Showing 6 changed files with 65 additions and 56 deletions.
41 changes: 29 additions & 12 deletions ArtAPI/PixivAPI.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
using System.Collections.Generic;
using System.Linq;
using System.Net.Http;
using System.Net.Http.Headers;
using System.Threading.Tasks;
using ArtAPI.misc;

Expand All @@ -11,6 +12,7 @@ namespace ArtAPI
public sealed class PixivAPI : RequestArt
{
private const string
LOGIN_URL = @"https://app-api.pixiv.net/web/v1/login?{0}",
AUTH_URL = @"https://oauth.secure.pixiv.net/auth/token",
LOGIN_SECRET = "28c1fdd170a5204386cb1313c7077b34f83e4aaf4aa829ce78c231e05b0bae2c",
APIUrlWithLogin = @"https://app-api.pixiv.net/v1/user/illusts?user_id={0}",
Expand All @@ -19,13 +21,16 @@ private const string
IllustProjectUrl = @"https://www.pixiv.net/touch/ajax/illust/details?illust_id={0}", // one project can contain multiple illustrations
ArtistDetails = @"https://www.pixiv.net/touch/ajax/user/details?id={0}";

private string _artistName;
private string _artistName, code_verifier;

public string RefreshToken { get; private set; }

public PixivAPI()
{
Client.DefaultRequestHeaders.Referrer = new Uri("https://www.pixiv.net");
Client.DefaultRequestHeaders.UserAgent.Clear();
Client.DefaultRequestHeaders.UserAgent.ParseAdd("PixivAndroidApp/5.0.115 (Android 6.0; ArtifyApp)");
Client.DefaultRequestHeaders.Host = "oauth.secure.pixiv.net";
}

public override async Task<Uri> CreateUrlFromName(string artistName)
Expand Down Expand Up @@ -133,7 +138,7 @@ private async Task GetImageURLsWithoutLoginAsync(string illustProject)
{
var response = await Client.GetStringAsyncM(illustProject).ConfigureAwait(false);
var illustDetails = JObject.Parse(response)["body"]["illust_details"];
if (Int32.Parse(illustDetails["page_count"].ToString()) > 1)
if (int.Parse(illustDetails["page_count"].ToString()) > 1)
{
foreach (var img_url in illustDetails["manga_a"])
{
Expand Down Expand Up @@ -168,7 +173,7 @@ private void GetImageURLsWithLogin(JObject responseJson)
{
foreach (var IllustDetails in responseJson["illusts"])
{
if (Int32.Parse(IllustDetails["page_count"].ToString()) > 1)
if (int.Parse(IllustDetails["page_count"].ToString()) > 1)
{
foreach (var img_url in IllustDetails["meta_pages"])
{
Expand Down Expand Up @@ -209,8 +214,8 @@ public override async Task<bool> Auth(string refreshToken)
var clientTime = DateTime.UtcNow.ToString("s") + "+00:00";
var data = new Dictionary<string, string>()
{
{"client_id", "KzEZED7aC0vird8jWyHM38mXjNTY" },
{"client_secret", "W9JZoJe00qPvJsiyCGT3CCtC6ZUtdpKpzMbNlUGP" },
{"client_id", "MOBrBDS8blbauoSck0ZfDbtuzpyT" },
{"client_secret", "lsACyCD94FhDUtGTXi3QzcFE2uU1hqtDaKeqrdwj" },
{"get_secure_url", "1" },
{"grant_type", "refresh_token" },
{"refresh_token", refreshToken },
Expand Down Expand Up @@ -247,19 +252,31 @@ public override async Task<bool> Auth(string refreshToken)
OnLoginStatusChanged(new LoginStatusChangedEventArgs(LoginStatus.LoggedIn));
return IsLoggedIn = true;
}
/// <summary>
/// Creates URL for the OAuth authentication
/// </summary>
/// <returns>url with authentication params</returns>
public string Pkce()
{
code_verifier = General.UrlsafeToken();
var codeChallenge = General.CreateS256(code_verifier);
return string.Format(LOGIN_URL,
$"code_challenge={codeChallenge}&code_challenge_method=S256&client=pixiv-android");
}

public override async Task<string> Login(string username, string password)
public async Task<string> Login(string code)
{
OnLoginStatusChanged(new LoginStatusChangedEventArgs(LoginStatus.LoggingIn));
var clientTime = DateTime.UtcNow.ToString("s") + "+00:00";
var data = new Dictionary<string, string>()
{
{"client_id", "KzEZED7aC0vird8jWyHM38mXjNTY" },
{"client_secret", "W9JZoJe00qPvJsiyCGT3CCtC6ZUtdpKpzMbNlUGP" },
{"get_secure_url", "1" },
{"grant_type", "password" },
{"username", username },
{"password", password }
{"client_id", "MOBrBDS8blbauoSck0ZfDbtuzpyT" },
{"client_secret", "lsACyCD94FhDUtGTXi3QzcFE2uU1hqtDaKeqrdwj" },
{"grant_type", "authorization_code" },
{"include_policy", "true"},
{"code", code},
{"code_verifier", code_verifier},
{"redirect_uri", "https://app-api.pixiv.net/web/v1/users/auth/pixiv/callback"}
};
if (Client.DefaultRequestHeaders.Contains("X-Client-Time"))
{
Expand Down
27 changes: 27 additions & 0 deletions ArtAPI/misc/General.cs
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,33 @@ public static string CreateMD5(string input)
return sb.ToString();
}
}

public static string CreateS256(string data)
{
using var sha256 = SHA256.Create();
var hash = sha256.ComputeHash(Encoding.ASCII.GetBytes(data));
var hashstr = Convert.ToBase64String(hash);
return hashstr.ToUrlSafeString();
}

public static string ToUrlSafeString(this string s)
{
return s.Replace("=", "").Replace("+", "-").Replace("/", "_");
}
/// <summary>
/// Creates a random URL-safe text string, in Base64 encoding.
/// </summary>
/// <param name="nbytes">token length in bytes</param>
/// <returns>url safe token</returns>
public static string UrlsafeToken(int nbytes = 32)
{
var buffer = new byte[nbytes];
new Random().NextBytes(buffer);
var token = Convert.ToBase64String(buffer).ToUrlSafeString();
return token;
}


/// <summary>
/// special characters which cannot be used for file and directory names
/// </summary>
Expand Down
1 change: 1 addition & 0 deletions Artify/Artify.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
</PropertyGroup>

<ItemGroup>
<PackageReference Include="CefSharp.Wpf.NETCore" Version="93.1.140" />
<PackageReference Include="PropertyChanged.Fody" Version="3.2.8" />
</ItemGroup>

Expand Down
14 changes: 7 additions & 7 deletions Artify/ViewModels/SettingsViewModel.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
using System.Threading.Tasks;
using ArtAPI;
using ArtAPI.misc;
using Artify.Models;

Expand All @@ -7,10 +8,9 @@ namespace Artify.ViewModels
public class SettingsViewModel : BaseViewModel
{
private readonly ArtifyModel _artifyModel;
public string UserName { get; set; }
public string UserPassword { private get; set; }
public bool IsLoginInputValid { get; set; } = true;
public bool IsInputEnabled { get; set; } = true;
public bool OpenBrowser { get; set; } = false;
public string LoginUrl { get; set; }
public string LoginNotification { get; set; }
public string SaveLocation
{
Expand Down Expand Up @@ -77,19 +77,19 @@ private void Platform_LoginStatusChanged(object sender, LoginStatusChangedEventA

private async Task Login()
{
if (string.IsNullOrEmpty(UserName) || string.IsNullOrEmpty(UserPassword)) return;
var pixiv = (PixivAPI) _artifyModel.Platform;
IsInputEnabled = false;
LoginUrl = pixiv.Pkce();
OpenBrowser = true;
if (await _artifyModel.Platform.Login(UserName, UserPassword) is { } result)
{
_artifyModel.settings.pixiv_refresh_token = result;
_artifyModel.UpdateSettings();
}
else
{
IsLoginInputValid = false;
LoginNotification = "Incorrect username or password";
LoginNotification = "Something went wrong";
}
UserPassword = null;
IsInputEnabled = true;
}
}
Expand Down
7 changes: 1 addition & 6 deletions Artify/Views/SettingsPopUp.xaml
Original file line number Diff line number Diff line change
Expand Up @@ -59,12 +59,7 @@
</Border.Style>
<StackPanel>
<Label Content="Pixiv Login" Foreground="white" FontSize="18"/>
<Label Content="Username" Foreground="white" FontSize="10" Margin="5,0,0,-10"/>
<TextBox Name="username" Style="{StaticResource InputFieldStyle}" Text="{Binding UserName}" Margin="10" Tag="{Binding IsLoginInputValid, Mode=TwoWay}" GotFocus="InputFieldGotFocus"/>
<Label Content="Password" Foreground="white" FontSize="10" Margin="5,-10"/>
<PasswordBox Name="password" Style="{StaticResource PasswordInputFieldStyle}" Margin="10" Tag="{Binding IsLoginInputValid, Mode=TwoWay}" PasswordChanged="PasswordBox_PasswordChanged" GotFocus="InputFieldGotFocus"/>
<Label Content="{Binding LoginNotification}" HorizontalAlignment="Center" FontSize="15" Foreground="Tomato"/>
<Button Name="LoginButton" Style="{StaticResource MaterialButtonStyle}" Content="Login" Margin="100,5,100,15" FontSize="20" Command="{Binding LoginCommand}" Click="LoginButtonClick" IsEnabled="{Binding IsInputEnabled, Mode=TwoWay}"/>
<Button Name="LoginButton" Style="{StaticResource MaterialButtonStyle}" Content="Login" Margin="100,25,100,15" FontSize="20" Command="{Binding LoginCommand}" Click="LoginButtonClick" IsEnabled="{Binding IsInputEnabled, Mode=TwoWay}"/>
</StackPanel>
</Border>
<Border Padding="5" BorderBrush="DarkSlateGray" BorderThickness="0,0,0,1" Margin="0,5">
Expand Down
31 changes: 0 additions & 31 deletions Artify/Views/SettingsPopUp.xaml.cs
Original file line number Diff line number Diff line change
Expand Up @@ -38,45 +38,14 @@ private void CloseButton_Click(object sender, RoutedEventArgs e)
Close();
}

private void PasswordBox_PasswordChanged(object sender, RoutedEventArgs e)
{
if (DataContext != null)
{ ((dynamic)DataContext).UserPassword = ((PasswordBox)sender).Password; }
}

private void LoginButtonClick(object sender, RoutedEventArgs e)
{
if (username.Text.Length == 0) username.Tag = false;
if (password.Password.Length == 0) password.Tag = false;
}

private void InputFieldGotFocus(object sender, RoutedEventArgs e)
{
username.Tag = password.Tag = true;
}

private void Window_KeyDown(object sender, System.Windows.Input.KeyEventArgs e)
{
if (e.Key == System.Windows.Input.Key.Escape) Close();
}

private void TextBox_PreviewTextInput(object sender, System.Windows.Input.TextCompositionEventArgs e)
{
// make sure that input contains only numbers
if (!int.TryParse(e.Text, out _) || string.IsNullOrWhiteSpace(e.Text))
e.Handled = true;
}

private void TextBox_PreviewKeyDown(object sender, System.Windows.Input.KeyEventArgs e)
{
// make sure that input doesn't contain any whitespace
switch (e.Key)
{
case Key.Space:
case Key.V when (Keyboard.Modifiers & ModifierKeys.Control) == ModifierKeys.Control:
e.Handled = true;
break;
}
}
}
}

0 comments on commit 901b5a8

Please sign in to comment.