mirror of
https://github.com/Rast1234/VkNet.TokenMagic.git
synced 2026-04-26 11:10:36 +00:00
refactor as vknet extension
This commit is contained in:
commit
cc36c930ec
18 changed files with 1285 additions and 0 deletions
337
.gitignore
vendored
Normal file
337
.gitignore
vendored
Normal file
|
|
@ -0,0 +1,337 @@
|
|||
## Ignore Visual Studio temporary files, build results, and
|
||||
## files generated by popular Visual Studio add-ons.
|
||||
##
|
||||
## Get latest from https://github.com/github/gitignore/blob/master/VisualStudio.gitignore
|
||||
|
||||
# User-specific files
|
||||
*.rsuser
|
||||
*.suo
|
||||
*.user
|
||||
*.userosscache
|
||||
*.sln.docstates
|
||||
|
||||
# User-specific files (MonoDevelop/Xamarin Studio)
|
||||
*.userprefs
|
||||
|
||||
# Build results
|
||||
[Dd]ebug/
|
||||
[Dd]ebugPublic/
|
||||
[Rr]elease/
|
||||
[Rr]eleases/
|
||||
x64/
|
||||
x86/
|
||||
bld/
|
||||
[Bb]in/
|
||||
[Oo]bj/
|
||||
[Ll]og/
|
||||
|
||||
# Visual Studio 2015/2017 cache/options directory
|
||||
.vs/
|
||||
# Uncomment if you have tasks that create the project's static files in wwwroot
|
||||
#wwwroot/
|
||||
|
||||
# Visual Studio 2017 auto generated files
|
||||
Generated\ Files/
|
||||
|
||||
# MSTest test Results
|
||||
[Tt]est[Rr]esult*/
|
||||
[Bb]uild[Ll]og.*
|
||||
|
||||
# NUNIT
|
||||
*.VisualState.xml
|
||||
TestResult.xml
|
||||
|
||||
# Build Results of an ATL Project
|
||||
[Dd]ebugPS/
|
||||
[Rr]eleasePS/
|
||||
dlldata.c
|
||||
|
||||
# Benchmark Results
|
||||
BenchmarkDotNet.Artifacts/
|
||||
|
||||
# .NET Core
|
||||
project.lock.json
|
||||
project.fragment.lock.json
|
||||
artifacts/
|
||||
|
||||
# StyleCop
|
||||
StyleCopReport.xml
|
||||
|
||||
# Files built by Visual Studio
|
||||
*_i.c
|
||||
*_p.c
|
||||
*_h.h
|
||||
*.ilk
|
||||
*.meta
|
||||
*.obj
|
||||
*.iobj
|
||||
*.pch
|
||||
*.pdb
|
||||
*.ipdb
|
||||
*.pgc
|
||||
*.pgd
|
||||
*.rsp
|
||||
*.sbr
|
||||
*.tlb
|
||||
*.tli
|
||||
*.tlh
|
||||
*.tmp
|
||||
*.tmp_proj
|
||||
*_wpftmp.csproj
|
||||
*.log
|
||||
*.vspscc
|
||||
*.vssscc
|
||||
.builds
|
||||
*.pidb
|
||||
*.svclog
|
||||
*.scc
|
||||
|
||||
# Chutzpah Test files
|
||||
_Chutzpah*
|
||||
|
||||
# Visual C++ cache files
|
||||
ipch/
|
||||
*.aps
|
||||
*.ncb
|
||||
*.opendb
|
||||
*.opensdf
|
||||
*.sdf
|
||||
*.cachefile
|
||||
*.VC.db
|
||||
*.VC.VC.opendb
|
||||
|
||||
# Visual Studio profiler
|
||||
*.psess
|
||||
*.vsp
|
||||
*.vspx
|
||||
*.sap
|
||||
|
||||
# Visual Studio Trace Files
|
||||
*.e2e
|
||||
|
||||
# TFS 2012 Local Workspace
|
||||
$tf/
|
||||
|
||||
# Guidance Automation Toolkit
|
||||
*.gpState
|
||||
|
||||
# ReSharper is a .NET coding add-in
|
||||
_ReSharper*/
|
||||
*.[Rr]e[Ss]harper
|
||||
*.DotSettings.user
|
||||
|
||||
# JustCode is a .NET coding add-in
|
||||
.JustCode
|
||||
|
||||
# TeamCity is a build add-in
|
||||
_TeamCity*
|
||||
|
||||
# DotCover is a Code Coverage Tool
|
||||
*.dotCover
|
||||
|
||||
# AxoCover is a Code Coverage Tool
|
||||
.axoCover/*
|
||||
!.axoCover/settings.json
|
||||
|
||||
# Visual Studio code coverage results
|
||||
*.coverage
|
||||
*.coveragexml
|
||||
|
||||
# NCrunch
|
||||
_NCrunch_*
|
||||
.*crunch*.local.xml
|
||||
nCrunchTemp_*
|
||||
|
||||
# MightyMoose
|
||||
*.mm.*
|
||||
AutoTest.Net/
|
||||
|
||||
# Web workbench (sass)
|
||||
.sass-cache/
|
||||
|
||||
# Installshield output folder
|
||||
[Ee]xpress/
|
||||
|
||||
# DocProject is a documentation generator add-in
|
||||
DocProject/buildhelp/
|
||||
DocProject/Help/*.HxT
|
||||
DocProject/Help/*.HxC
|
||||
DocProject/Help/*.hhc
|
||||
DocProject/Help/*.hhk
|
||||
DocProject/Help/*.hhp
|
||||
DocProject/Help/Html2
|
||||
DocProject/Help/html
|
||||
|
||||
# Click-Once directory
|
||||
publish/
|
||||
|
||||
# Publish Web Output
|
||||
*.[Pp]ublish.xml
|
||||
*.azurePubxml
|
||||
# Note: Comment the next line if you want to checkin your web deploy settings,
|
||||
# but database connection strings (with potential passwords) will be unencrypted
|
||||
*.pubxml
|
||||
*.publishproj
|
||||
|
||||
# Microsoft Azure Web App publish settings. Comment the next line if you want to
|
||||
# checkin your Azure Web App publish settings, but sensitive information contained
|
||||
# in these scripts will be unencrypted
|
||||
PublishScripts/
|
||||
|
||||
# NuGet Packages
|
||||
*.nupkg
|
||||
# The packages folder can be ignored because of Package Restore
|
||||
**/[Pp]ackages/*
|
||||
# except build/, which is used as an MSBuild target.
|
||||
!**/[Pp]ackages/build/
|
||||
# Uncomment if necessary however generally it will be regenerated when needed
|
||||
#!**/[Pp]ackages/repositories.config
|
||||
# NuGet v3's project.json files produces more ignorable files
|
||||
*.nuget.props
|
||||
*.nuget.targets
|
||||
|
||||
# Microsoft Azure Build Output
|
||||
csx/
|
||||
*.build.csdef
|
||||
|
||||
# Microsoft Azure Emulator
|
||||
ecf/
|
||||
rcf/
|
||||
|
||||
# Windows Store app package directories and files
|
||||
AppPackages/
|
||||
BundleArtifacts/
|
||||
Package.StoreAssociation.xml
|
||||
_pkginfo.txt
|
||||
*.appx
|
||||
|
||||
# Visual Studio cache files
|
||||
# files ending in .cache can be ignored
|
||||
*.[Cc]ache
|
||||
# but keep track of directories ending in .cache
|
||||
!*.[Cc]ache/
|
||||
|
||||
# Others
|
||||
ClientBin/
|
||||
~$*
|
||||
*~
|
||||
*.dbmdl
|
||||
*.dbproj.schemaview
|
||||
*.jfm
|
||||
*.pfx
|
||||
*.publishsettings
|
||||
orleans.codegen.cs
|
||||
|
||||
# Including strong name files can present a security risk
|
||||
# (https://github.com/github/gitignore/pull/2483#issue-259490424)
|
||||
#*.snk
|
||||
|
||||
# Since there are multiple workflows, uncomment next line to ignore bower_components
|
||||
# (https://github.com/github/gitignore/pull/1529#issuecomment-104372622)
|
||||
#bower_components/
|
||||
|
||||
# RIA/Silverlight projects
|
||||
Generated_Code/
|
||||
|
||||
# Backup & report files from converting an old project file
|
||||
# to a newer Visual Studio version. Backup files are not needed,
|
||||
# because we have git ;-)
|
||||
_UpgradeReport_Files/
|
||||
Backup*/
|
||||
UpgradeLog*.XML
|
||||
UpgradeLog*.htm
|
||||
ServiceFabricBackup/
|
||||
*.rptproj.bak
|
||||
|
||||
# SQL Server files
|
||||
*.mdf
|
||||
*.ldf
|
||||
*.ndf
|
||||
|
||||
# Business Intelligence projects
|
||||
*.rdl.data
|
||||
*.bim.layout
|
||||
*.bim_*.settings
|
||||
*.rptproj.rsuser
|
||||
|
||||
# Microsoft Fakes
|
||||
FakesAssemblies/
|
||||
|
||||
# GhostDoc plugin setting file
|
||||
*.GhostDoc.xml
|
||||
|
||||
# Node.js Tools for Visual Studio
|
||||
.ntvs_analysis.dat
|
||||
node_modules/
|
||||
|
||||
# Visual Studio 6 build log
|
||||
*.plg
|
||||
|
||||
# Visual Studio 6 workspace options file
|
||||
*.opt
|
||||
|
||||
# Visual Studio 6 auto-generated workspace file (contains which files were open etc.)
|
||||
*.vbw
|
||||
|
||||
# Visual Studio LightSwitch build output
|
||||
**/*.HTMLClient/GeneratedArtifacts
|
||||
**/*.DesktopClient/GeneratedArtifacts
|
||||
**/*.DesktopClient/ModelManifest.xml
|
||||
**/*.Server/GeneratedArtifacts
|
||||
**/*.Server/ModelManifest.xml
|
||||
_Pvt_Extensions
|
||||
|
||||
# Paket dependency manager
|
||||
.paket/paket.exe
|
||||
paket-files/
|
||||
|
||||
# FAKE - F# Make
|
||||
.fake/
|
||||
|
||||
# JetBrains Rider
|
||||
.idea/
|
||||
*.sln.iml
|
||||
|
||||
# CodeRush personal settings
|
||||
.cr/personal
|
||||
|
||||
# Python Tools for Visual Studio (PTVS)
|
||||
__pycache__/
|
||||
*.pyc
|
||||
|
||||
# Cake - Uncomment if you are using it
|
||||
# tools/**
|
||||
# !tools/packages.config
|
||||
|
||||
# Tabs Studio
|
||||
*.tss
|
||||
|
||||
# Telerik's JustMock configuration file
|
||||
*.jmconfig
|
||||
|
||||
# BizTalk build output
|
||||
*.btp.cs
|
||||
*.btm.cs
|
||||
*.odx.cs
|
||||
*.xsd.cs
|
||||
|
||||
# OpenCover UI analysis results
|
||||
OpenCover/
|
||||
|
||||
# Azure Stream Analytics local run output
|
||||
ASALocalRun/
|
||||
|
||||
# MSBuild Binary and Structured Log
|
||||
*.binlog
|
||||
|
||||
# NVidia Nsight GPU debugger configuration file
|
||||
*.nvuser
|
||||
|
||||
# MFractors (Xamarin productivity tool) working folder
|
||||
.mfractor/
|
||||
|
||||
# Local History for Visual Studio
|
||||
.localhistory/
|
||||
|
||||
**/FileCache.*.json
|
||||
**logs/
|
||||
25
Example/Example.csproj
Normal file
25
Example/Example.csproj
Normal file
|
|
@ -0,0 +1,25 @@
|
|||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<OutputType>Exe</OutputType>
|
||||
<TargetFramework>netcoreapp2.1</TargetFramework>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<None Remove="nlog.config" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<EmbeddedResource Include="nlog.config">
|
||||
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
|
||||
</EmbeddedResource>
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="2.2.0" />
|
||||
<PackageReference Include="Microsoft.Extensions.Http" Version="2.2.0" />
|
||||
<PackageReference Include="VkNet" Version="1.41.0" />
|
||||
<PackageReference Include="VkNet.NLog.Extensions.Logging" Version="1.3.1" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
58
Example/Program.cs
Normal file
58
Example/Program.cs
Normal file
|
|
@ -0,0 +1,58 @@
|
|||
using System;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using VkNet;
|
||||
using VkNet.Enums.Filters;
|
||||
using VkNet.Model;
|
||||
using VkNet.Model.RequestParams;
|
||||
using VkNet.NLog.Extensions.Logging;
|
||||
using VkNet.NLog.Extensions.Logging.Extensions;
|
||||
using VkNet.TokenMagic;
|
||||
|
||||
namespace Example
|
||||
{
|
||||
class Program
|
||||
{
|
||||
private static void Main(string[] args)
|
||||
{
|
||||
var services = new ServiceCollection();
|
||||
services.AddVkTokenMagic();
|
||||
services.AddSingleton<ILoggerFactory, LoggerFactory>();
|
||||
services.AddSingleton(typeof(ILogger<>), typeof(Logger<>));
|
||||
services.AddLogging(builder =>
|
||||
{
|
||||
builder.ClearProviders();
|
||||
builder.SetMinimumLevel(LogLevel.Trace);
|
||||
builder.AddNLog(new NLogProviderOptions
|
||||
{
|
||||
CaptureMessageProperties = true,
|
||||
CaptureMessageTemplates = true
|
||||
});
|
||||
});
|
||||
NLog.LogManager.LoadConfiguration("nlog.config");
|
||||
|
||||
var vkNet = new VkApi(services);
|
||||
vkNet.Authorize(new ApiAuthParams
|
||||
{
|
||||
Login = "LOGIN",
|
||||
Password = "PASSWORD",
|
||||
Settings = Settings.Audio
|
||||
|
||||
});
|
||||
|
||||
var audios = vkNet.Audio.Get(new AudioGetParams
|
||||
{
|
||||
Count = 10
|
||||
});
|
||||
|
||||
foreach (var audio in audios)
|
||||
{
|
||||
Console.WriteLine($"{audio.Artist} - {audio.Title} {audio.Url}");
|
||||
}
|
||||
Console.ReadLine();
|
||||
NLog.LogManager.Shutdown();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
14
Example/nlog.config
Normal file
14
Example/nlog.config
Normal file
|
|
@ -0,0 +1,14 @@
|
|||
<?xml version="1.0" encoding="utf-8" ?>
|
||||
<nlog xmlns="http://www.nlog-project.org/schemas/NLog.xsd"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
|
||||
|
||||
<targets>
|
||||
<target name="logfile" xsi:type="File" fileName="file.txt" layout="${longdate} ${callsite} ${level} ${message}" />
|
||||
<target name="logconsole" xsi:type="Console" layout="${longdate} ${callsite} ${level} ${message}" />
|
||||
</targets>
|
||||
|
||||
<rules>
|
||||
<logger name="*" minlevel="Trace" writeTo="logconsole" />
|
||||
<logger name="*" minlevel="Trace" writeTo="logfile" />
|
||||
</rules>
|
||||
</nlog>
|
||||
31
VkNet.TokenMagic.sln
Normal file
31
VkNet.TokenMagic.sln
Normal file
|
|
@ -0,0 +1,31 @@
|
|||
|
||||
Microsoft Visual Studio Solution File, Format Version 12.00
|
||||
# Visual Studio 15
|
||||
VisualStudioVersion = 15.0.28307.168
|
||||
MinimumVisualStudioVersion = 10.0.40219.1
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "VkNet.TokenMagic", "VkNet.TokenMagic\VkNet.TokenMagic.csproj", "{EB2F6E3D-128C-42D4-8DD9-71FEB7A86650}"
|
||||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Example", "Example\Example.csproj", "{C2D199CE-608E-47C6-BF63-207F9E508768}"
|
||||
EndProject
|
||||
Global
|
||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||
Debug|Any CPU = Debug|Any CPU
|
||||
Release|Any CPU = Release|Any CPU
|
||||
EndGlobalSection
|
||||
GlobalSection(ProjectConfigurationPlatforms) = postSolution
|
||||
{EB2F6E3D-128C-42D4-8DD9-71FEB7A86650}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{EB2F6E3D-128C-42D4-8DD9-71FEB7A86650}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{EB2F6E3D-128C-42D4-8DD9-71FEB7A86650}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{EB2F6E3D-128C-42D4-8DD9-71FEB7A86650}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{C2D199CE-608E-47C6-BF63-207F9E508768}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{C2D199CE-608E-47C6-BF63-207F9E508768}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{C2D199CE-608E-47C6-BF63-207F9E508768}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{C2D199CE-608E-47C6-BF63-207F9E508768}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
EndGlobalSection
|
||||
GlobalSection(SolutionProperties) = preSolution
|
||||
HideSolutionNode = FALSE
|
||||
EndGlobalSection
|
||||
GlobalSection(ExtensibilityGlobals) = postSolution
|
||||
SolutionGuid = {6B73C46D-325F-496A-8E8B-405E063619A7}
|
||||
EndGlobalSection
|
||||
EndGlobal
|
||||
62
VkNet.TokenMagic/Extensions.cs
Normal file
62
VkNet.TokenMagic/Extensions.cs
Normal file
|
|
@ -0,0 +1,62 @@
|
|||
using System;
|
||||
using System.Net;
|
||||
using System.Net.Http;
|
||||
using System.Net.Security;
|
||||
using System.Security.Cryptography.X509Certificates;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Microsoft.Extensions.DependencyInjection.Extensions;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using VkNet.Abstractions.Utils;
|
||||
using VkNet.TokenMagic.Services;
|
||||
using VkNet.TokenMagic.Services.Token.Google;
|
||||
using VkNet.Utils;
|
||||
|
||||
namespace VkNet.TokenMagic
|
||||
{
|
||||
public static class Extensions
|
||||
{
|
||||
public static IServiceCollection AddTokenMagic(this IServiceCollection services)
|
||||
{
|
||||
services.TryAddSingleton<RandomAppIdProvider>();
|
||||
services.TryAddSingleton<MTalkTcpClient>();
|
||||
services.TryAddSingleton<AndroidHttpClient>();
|
||||
services.TryAddSingleton<GoogleSecurityHttpClient>();
|
||||
services.TryAddSingleton<ReceiptReceiver>();
|
||||
|
||||
services.TryAddSingleton<IRestClient, RestClientWithUserAgent>(); // vknet needs user-agent too
|
||||
|
||||
services.AddHttpClient<AndroidHttpClient>().ConfigurePrimaryHttpMessageHandler(provider => MaybeProxyHttpHandler(provider)).SetHandlerLifetime(TimeSpan.FromMinutes(1));
|
||||
services.AddHttpClient<GoogleSecurityHttpClient>().ConfigurePrimaryHttpMessageHandler(provider => MaybeProxyHttpHandler(provider, true)).SetHandlerLifetime(TimeSpan.FromMinutes(1));
|
||||
services.AddHttpClient<IRestClient, RestClientWithUserAgent>().ConfigurePrimaryHttpMessageHandler(provider => MaybeProxyHttpHandler(provider)).SetHandlerLifetime(TimeSpan.FromMinutes(10));
|
||||
|
||||
services.TryAddSingleton<IBrowser, BrowserWithAndroidToken>();
|
||||
|
||||
return services;
|
||||
}
|
||||
|
||||
public static HttpClientHandler MaybeProxyHttpHandler(IServiceProvider provider, bool ignoreSsl=false)
|
||||
{
|
||||
var proxy = provider.GetService<IWebProxy>();
|
||||
var logger = provider.GetService<ILogger<HttpClient>>();
|
||||
var useProxyCondition = proxy != null;
|
||||
if (useProxyCondition)
|
||||
{
|
||||
logger?.LogDebug($"Use Proxy: {proxy}");
|
||||
}
|
||||
|
||||
Func<HttpRequestMessage, X509Certificate2, X509Chain, SslPolicyErrors, bool> certCallback = null;
|
||||
if (ignoreSsl)
|
||||
{
|
||||
certCallback = (message, certificate2, arg3, arg4) => true;
|
||||
logger?.LogDebug($"Ignoring ssl");
|
||||
}
|
||||
|
||||
return new HttpClientHandler
|
||||
{
|
||||
Proxy = proxy,
|
||||
UseProxy = useProxyCondition,
|
||||
ServerCertificateCustomValidationCallback = certCallback
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
16
VkNet.TokenMagic/Models/Google/GoogleCredentials.cs
Normal file
16
VkNet.TokenMagic/Models/Google/GoogleCredentials.cs
Normal file
|
|
@ -0,0 +1,16 @@
|
|||
using System.Collections.Generic;
|
||||
|
||||
namespace VkNet.TokenMagic.Models.Google
|
||||
{
|
||||
public class GoogleCredentials
|
||||
{
|
||||
public long Id { get; set; }
|
||||
public long Token { get; set; }
|
||||
public List<byte> RawId { get; set; }
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
return $"{nameof(GoogleCredentials)}(id {Id}; token {Token})";
|
||||
}
|
||||
}
|
||||
}
|
||||
14
VkNet.TokenMagic/Models/Google/ProtobufField.cs
Normal file
14
VkNet.TokenMagic/Models/Google/ProtobufField.cs
Normal file
|
|
@ -0,0 +1,14 @@
|
|||
namespace VkNet.TokenMagic.Models.Google
|
||||
{
|
||||
public class ProtobufField
|
||||
{
|
||||
public ProtobufField(int value)
|
||||
{
|
||||
Type = value & 0x7; // last three bits, 0b00000111
|
||||
FieldNumber = value >> 3;
|
||||
}
|
||||
|
||||
public int Type { get; }
|
||||
public int FieldNumber { get; }
|
||||
}
|
||||
}
|
||||
184
VkNet.TokenMagic/Services/BrowserWithAndroidToken.cs
Normal file
184
VkNet.TokenMagic/Services/BrowserWithAndroidToken.cs
Normal file
|
|
@ -0,0 +1,184 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Net;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Newtonsoft.Json;
|
||||
using Newtonsoft.Json.Linq;
|
||||
using Newtonsoft.Json.Serialization;
|
||||
using VkNet.Abstractions.Core;
|
||||
using VkNet.Abstractions.Utils;
|
||||
using VkNet.Enums.SafetyEnums;
|
||||
using VkNet.Exception;
|
||||
using VkNet.Model;
|
||||
using VkNet.TokenMagic.Services.Token.Google;
|
||||
using VkNet.Utils;
|
||||
|
||||
namespace VkNet.TokenMagic.Services
|
||||
{
|
||||
public class BrowserWithAndroidToken : IBrowser
|
||||
{
|
||||
public BrowserWithAndroidToken(IVkApiVersionManager versionManager, IRestClient restClient, ReceiptReceiver receiptReceiver, ILogger<BrowserWithAndroidToken> logger)
|
||||
{
|
||||
_versionManager = versionManager;
|
||||
_restClient = restClient;
|
||||
_receiptReceiver = receiptReceiver;
|
||||
_logger = logger;
|
||||
}
|
||||
|
||||
public IWebProxy Proxy { get; set; }
|
||||
|
||||
public AuthorizationResult Authorize()
|
||||
{
|
||||
var authResult = BaseAuth();
|
||||
var receipt = _receiptReceiver.GetReceipt().ConfigureAwait(false).GetAwaiter().GetResult();
|
||||
if (receipt == null)
|
||||
{
|
||||
throw new VkApiException("receipt is null");
|
||||
}
|
||||
|
||||
var newToken = RefreshToken(authResult.AccessToken, receipt);
|
||||
|
||||
return new AuthorizationResult
|
||||
{
|
||||
AccessToken = newToken,
|
||||
ExpiresIn = authResult.ExpiresIn,
|
||||
UserId = authResult.UserId
|
||||
};
|
||||
}
|
||||
|
||||
public void SetAuthParams(IApiAuthParams authParams)
|
||||
{
|
||||
_apiAuthParams = authParams;
|
||||
}
|
||||
|
||||
private AuthorizationResult BaseAuth(string code = null)
|
||||
{
|
||||
if (string.IsNullOrEmpty(code))
|
||||
_logger?.LogDebug("1. Авторизация.");
|
||||
|
||||
var response = Invoke("https://oauth.vk.com/token",
|
||||
new VkParameters
|
||||
{
|
||||
{"grant_type", "password"},
|
||||
{"client_id", "2685278"},
|
||||
{"client_secret", "lxhD8OD7dMsqtXIm5IUY"},
|
||||
{"2fa_supported", true},
|
||||
{"username", $"{_apiAuthParams.Login}"},
|
||||
{"password", $"{_apiAuthParams.Password}"},
|
||||
{"code", code},
|
||||
{"scope", $"{_apiAuthParams.Settings}"},
|
||||
{"v", _versionManager.Version}
|
||||
});
|
||||
|
||||
var json = JObject.Parse(response);
|
||||
|
||||
var error = json["error"];
|
||||
|
||||
if (error == null)
|
||||
return json.ToObject<AuthorizationResult>(DefaultJsonSerializer);
|
||||
|
||||
switch (error.ToString())
|
||||
{
|
||||
case "need_validation":
|
||||
_logger?.LogDebug("1.1 Требуется код двухфакторной аутентификаци.");
|
||||
|
||||
if (_apiAuthParams.TwoFactorAuthorization == null)
|
||||
throw new ArgumentNullException(nameof(_apiAuthParams.TwoFactorAuthorization));
|
||||
|
||||
var result = _apiAuthParams.TwoFactorAuthorization.BeginInvoke(null, null);
|
||||
result.AsyncWaitHandle.WaitOne();
|
||||
var authCode = _apiAuthParams.TwoFactorAuthorization.EndInvoke(result);
|
||||
|
||||
return BaseAuth(authCode);
|
||||
case "invalid_request":
|
||||
case "invalid_client":
|
||||
var errorDescription = json["error_description"].ToString();
|
||||
throw new VkApiAuthorizationException(errorDescription, _apiAuthParams.Login,
|
||||
_apiAuthParams.Password);
|
||||
case "need_captcha":
|
||||
var sid = json["captcha_sid"].Value<long>();
|
||||
var imgUrl = json["captcha_img"].ToString();
|
||||
throw new CaptchaNeededException(sid, imgUrl);
|
||||
default:
|
||||
throw new VkApiException($"Неизвестная ошибка.{Environment.NewLine}{response}");
|
||||
}
|
||||
}
|
||||
|
||||
private string Invoke(string url, VkParameters parameters)
|
||||
{
|
||||
var response = _restClient.PostAsync(new Uri(url), parameters)
|
||||
.ConfigureAwait(false)
|
||||
.GetAwaiter()
|
||||
.GetResult();
|
||||
|
||||
var answer = response.Value ?? response.Message;
|
||||
|
||||
return answer;
|
||||
}
|
||||
|
||||
private string RefreshToken(string oldToken, string receipt)
|
||||
{
|
||||
_logger?.LogDebug("2. Обновление токена.");
|
||||
|
||||
var parameters = new List<KeyValuePair<string, string>>
|
||||
{
|
||||
new KeyValuePair<string, string>("access_token", oldToken),
|
||||
new KeyValuePair<string, string>("receipt", receipt),
|
||||
new KeyValuePair<string, string>("v", _versionManager.Version)
|
||||
};
|
||||
var httpResponse = _restClient.GetAsync(new Uri("https://api.vk.com/method/auth.refreshToken"), parameters)
|
||||
.ConfigureAwait(false)
|
||||
.GetAwaiter()
|
||||
.GetResult();
|
||||
var response = httpResponse.Value ?? httpResponse.Message;
|
||||
var jObject = JObject.Parse(response);
|
||||
var rawResponse = jObject["response"];
|
||||
return rawResponse["token"].ToString();
|
||||
}
|
||||
|
||||
#region Private Fields
|
||||
|
||||
private IApiAuthParams _apiAuthParams;
|
||||
|
||||
private readonly IVkApiVersionManager _versionManager;
|
||||
|
||||
private readonly IRestClient _restClient;
|
||||
private readonly ReceiptReceiver _receiptReceiver;
|
||||
|
||||
private readonly ILogger<BrowserWithAndroidToken> _logger;
|
||||
|
||||
private JsonSerializer DefaultJsonSerializer => new JsonSerializer
|
||||
{
|
||||
ContractResolver = new DefaultContractResolver
|
||||
{
|
||||
NamingStrategy = new SnakeCaseNamingStrategy()
|
||||
}
|
||||
};
|
||||
|
||||
#endregion
|
||||
|
||||
#region Not Implemented
|
||||
|
||||
public Uri CreateAuthorizeUrl(ulong clientId, ulong scope, Display display, string state)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
public AuthorizationResult Validate(string validateUrl, string phoneNumber)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
public string GetJson(string url, IEnumerable<KeyValuePair<string, string>> parameters)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
public AuthorizationResult Validate(string validateUrl)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
105
VkNet.TokenMagic/Services/RestClientWithUserAgent.cs
Normal file
105
VkNet.TokenMagic/Services/RestClientWithUserAgent.cs
Normal file
|
|
@ -0,0 +1,105 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Net;
|
||||
using System.Net.Http;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Newtonsoft.Json;
|
||||
using VkNet.Abstractions.Utils;
|
||||
using VkNet.Utils;
|
||||
|
||||
namespace VkNet.TokenMagic.Services
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public class RestClientWithUserAgent : IRestClient
|
||||
{
|
||||
// why is this in the interface?
|
||||
public IWebProxy Proxy { get => throw new NotImplementedException(); set => throw new NotImplementedException(); }
|
||||
|
||||
/// <summary>
|
||||
/// The log
|
||||
/// </summary>
|
||||
private readonly ILogger<RestClient> _logger;
|
||||
|
||||
private readonly HttpClient _httpClient;
|
||||
|
||||
protected readonly string UserAgent;
|
||||
|
||||
protected TimeSpan TimeoutSeconds;
|
||||
|
||||
/// <inheritdoc />
|
||||
public RestClientWithUserAgent(ILogger<RestClient> logger, HttpClient httpClient)
|
||||
:this(logger, httpClient, DefaultUserAgent)
|
||||
{
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
protected RestClientWithUserAgent(ILogger<RestClient> logger, HttpClient httpClient, string userAgent)
|
||||
{
|
||||
_logger = logger;
|
||||
_httpClient = httpClient;
|
||||
this.UserAgent = userAgent;
|
||||
_httpClient.DefaultRequestHeaders.UserAgent.ParseAdd(UserAgent);
|
||||
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public TimeSpan Timeout
|
||||
{
|
||||
get => TimeoutSeconds == TimeSpan.Zero ? TimeSpan.FromSeconds(300) : TimeoutSeconds;
|
||||
set => TimeoutSeconds = value;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public Task<HttpResponse<string>> GetAsync(Uri uri, IEnumerable<KeyValuePair<string, string>> parameters)
|
||||
{
|
||||
var queries = parameters
|
||||
.Where(parameter => !string.IsNullOrWhiteSpace(parameter.Value))
|
||||
.Select(parameter => $"{parameter.Key.ToLowerInvariant()}={parameter.Value}");
|
||||
|
||||
var url = new UriBuilder(uri)
|
||||
{
|
||||
Query = string.Join("&", queries)
|
||||
};
|
||||
|
||||
_logger?.LogDebug($"GET request: {url.Uri}");
|
||||
|
||||
var request = new HttpRequestMessage(HttpMethod.Get, url.Uri);
|
||||
|
||||
return CallAsync(httpClient => httpClient.SendAsync(request, HttpCompletionOption.ResponseHeadersRead));
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public Task<HttpResponse<string>> PostAsync(Uri uri, IEnumerable<KeyValuePair<string, string>> parameters)
|
||||
{
|
||||
if (_logger != null)
|
||||
{
|
||||
var json = JsonConvert.SerializeObject(parameters);
|
||||
_logger.LogDebug($"POST request: {uri}{Environment.NewLine}{Utilities.PrettyPrintJson(json)}");
|
||||
}
|
||||
|
||||
var content = new FormUrlEncodedContent(parameters);
|
||||
|
||||
var request = new HttpRequestMessage(HttpMethod.Post, uri) { Content = content };
|
||||
|
||||
return CallAsync(httpClient => httpClient.SendAsync(request, HttpCompletionOption.ResponseHeadersRead));
|
||||
}
|
||||
|
||||
protected async Task<HttpResponse<string>> CallAsync(Func<HttpClient, Task<HttpResponseMessage>> method)
|
||||
{
|
||||
var response = await method(_httpClient).ConfigureAwait(false);
|
||||
|
||||
var content = await response.Content.ReadAsStringAsync().ConfigureAwait(false);
|
||||
|
||||
_logger?.LogDebug($"Response:{Environment.NewLine}{Utilities.PrettyPrintJson(content)}");
|
||||
var url = response.RequestMessage.RequestUri.ToString();
|
||||
|
||||
return response.IsSuccessStatusCode
|
||||
? HttpResponse<string>.Success(response.StatusCode, content, url)
|
||||
: HttpResponse<string>.Fail(response.StatusCode, content, url);
|
||||
}
|
||||
|
||||
protected const string DefaultUserAgent = "KateMobileAndroid/51.2 lite-443 (Android 4.4.2; SDK 19; x86; unknown Android SDK built for x86; en)";
|
||||
}
|
||||
}
|
||||
44
VkNet.TokenMagic/Services/Token/Google/AndroidHttpClient.cs
Normal file
44
VkNet.TokenMagic/Services/Token/Google/AndroidHttpClient.cs
Normal file
|
|
@ -0,0 +1,44 @@
|
|||
using System;
|
||||
using System.Net.Http;
|
||||
using System.Net.Http.Headers;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.Extensions.Logging;
|
||||
|
||||
namespace VkNet.TokenMagic.Services.Token.Google
|
||||
{
|
||||
public class AndroidHttpClient
|
||||
{
|
||||
protected readonly HttpClient HttpClient;
|
||||
private readonly ILogger<AndroidHttpClient> _logger;
|
||||
private readonly string _appId;
|
||||
|
||||
public AndroidHttpClient(HttpClient httpClient, RandomAppIdProvider appIdProvider, ILogger<AndroidHttpClient> logger)
|
||||
{
|
||||
HttpClient = httpClient;
|
||||
_appId = appIdProvider.AppId;
|
||||
_logger = logger;
|
||||
}
|
||||
|
||||
public async Task<byte[]> CheckIn()
|
||||
{
|
||||
var request = new HttpRequestMessage(HttpMethod.Post, Url)
|
||||
{
|
||||
Method = HttpMethod.Post
|
||||
};
|
||||
request.Headers.Add("Expect", "");
|
||||
request.Content = new ByteArrayContent(queryMessage);
|
||||
request.Content.Headers.ContentType = MediaTypeHeaderValue.Parse("application/x-protobuffer");
|
||||
_logger?.LogDebug($"{nameof(CheckIn)}");
|
||||
var response = await HttpClient.SendAsync(request).ConfigureAwait(false);
|
||||
response.EnsureSuccessStatusCode();
|
||||
return await response.Content.ReadAsByteArrayAsync().ConfigureAwait(false);
|
||||
}
|
||||
|
||||
private static readonly byte[] queryMessage = {
|
||||
0x10, 0x00, 0x1a, 0x2a, 0x31, 0x2d, 0x39, 0x32, 0x39, 0x61, 0x30, 0x64, 0x63, 0x61, 0x30, 0x65, 0x65, 0x65, 0x35, 0x35, 0x35, 0x31, 0x33, 0x32, 0x38, 0x30, 0x31, 0x37, 0x31, 0x61, 0x38, 0x35, 0x38, 0x35, 0x64, 0x61, 0x37, 0x64, 0x63, 0x64, 0x33, 0x37, 0x30, 0x30, 0x66, 0x38, 0x22, 0xe3, 0x01, 0x0a, 0xbf, 0x01, 0x0a, 0x45, 0x67, 0x65, 0x6e, 0x65, 0x72, 0x69, 0x63, 0x5f, 0x78, 0x38, 0x36, 0x2f, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x5f, 0x73, 0x64, 0x6b, 0x5f, 0x78, 0x38, 0x36, 0x2f, 0x67, 0x65, 0x6e, 0x65, 0x72, 0x69, 0x63, 0x5f, 0x78, 0x38, 0x36, 0x3a, 0x34, 0x2e, 0x34, 0x2e, 0x32, 0x2f, 0x4b, 0x4b, 0x2f, 0x33, 0x30, 0x37, 0x39, 0x31, 0x38, 0x33, 0x3a, 0x65, 0x6e, 0x67, 0x2f, 0x74, 0x65, 0x73, 0x74, 0x2d, 0x6b, 0x65, 0x79, 0x73, 0x12, 0x06, 0x72, 0x61, 0x6e, 0x63, 0x68, 0x75, 0x1a, 0x0b, 0x67, 0x65, 0x6e, 0x65, 0x72, 0x69, 0x63, 0x5f, 0x78, 0x38, 0x36, 0x2a, 0x07, 0x75, 0x6e, 0x6b, 0x6e, 0x6f, 0x77, 0x6e, 0x32, 0x0e, 0x61, 0x6e, 0x64, 0x72, 0x6f, 0x69, 0x64, 0x2d, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x40, 0x85, 0xb5, 0x86, 0x06, 0x4a, 0x0b, 0x67, 0x65, 0x6e, 0x65, 0x72, 0x69, 0x63, 0x5f, 0x78, 0x38, 0x36, 0x50, 0x13, 0x5a, 0x19, 0x41, 0x6e, 0x64, 0x72, 0x6f, 0x69, 0x64, 0x20, 0x53, 0x44, 0x4b, 0x20, 0x62, 0x75, 0x69, 0x6c, 0x74, 0x20, 0x66, 0x6f, 0x72, 0x20, 0x78, 0x38, 0x36, 0x62, 0x07, 0x75, 0x6e, 0x6b, 0x6e, 0x6f, 0x77, 0x6e, 0x6a, 0x0e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x5f, 0x73, 0x64, 0x6b, 0x5f, 0x78, 0x38, 0x36, 0x70, 0x00, 0x10, 0x00, 0x32, 0x06, 0x33, 0x31, 0x30, 0x32, 0x36, 0x30, 0x3a, 0x06, 0x33, 0x31, 0x30, 0x32, 0x36, 0x30, 0x42, 0x0b, 0x6d, 0x6f, 0x62, 0x69, 0x6c, 0x65, 0x3a, 0x4c, 0x54, 0x45, 0x3a, 0x48, 0x00, 0x32, 0x05, 0x65, 0x6e, 0x5f, 0x55, 0x53, 0x38, 0xf0, 0xb4, 0xdf, 0xa6, 0xb9, 0x9a, 0xb8, 0x83, 0x8e, 0x01, 0x52, 0x0f, 0x33, 0x35, 0x38, 0x32, 0x34, 0x30, 0x30, 0x35, 0x31, 0x31, 0x31, 0x31, 0x31, 0x31, 0x30, 0x5a, 0x00, 0x62, 0x10, 0x41, 0x6d, 0x65, 0x72, 0x69, 0x63, 0x61, 0x2f, 0x4e, 0x65, 0x77, 0x5f, 0x59, 0x6f, 0x72, 0x6b, 0x70, 0x03, 0x7a, 0x1c, 0x37, 0x31, 0x51, 0x36, 0x52, 0x6e, 0x32, 0x44, 0x44, 0x5a, 0x6c, 0x31, 0x7a, 0x50, 0x44, 0x56, 0x61, 0x61, 0x65, 0x45, 0x48, 0x49, 0x74, 0x64, 0x2b, 0x59, 0x67, 0x3d, 0xa0, 0x01, 0x00, 0xb0, 0x01, 0x00
|
||||
};
|
||||
|
||||
private static readonly Uri Url = new Uri("https://android.clients.google.com/checkin");
|
||||
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,104 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Net.Http;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using VkNet.TokenMagic.Models.Google;
|
||||
|
||||
namespace VkNet.TokenMagic.Services.Token.Google
|
||||
{
|
||||
public class GoogleSecurityHttpClient {
|
||||
|
||||
protected readonly HttpClient HttpClient;
|
||||
private readonly string _appId;
|
||||
private readonly ILogger<GoogleSecurityHttpClient> _logger;
|
||||
|
||||
public GoogleSecurityHttpClient(HttpClient httpClient, RandomAppIdProvider appIdProvider, ILogger<GoogleSecurityHttpClient> logger)
|
||||
{
|
||||
HttpClient = httpClient;
|
||||
HttpClient.DefaultRequestHeaders.UserAgent.ParseAdd(GcmUserAgent);
|
||||
_appId = appIdProvider.AppId;
|
||||
_logger = logger;
|
||||
}
|
||||
|
||||
public async Task<string> GetReceipt(GoogleCredentials credentials)
|
||||
{
|
||||
|
||||
await RequestReceipt1(credentials).ConfigureAwait(false);
|
||||
return await RequestReceipt2(credentials).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
private async Task RequestReceipt1(GoogleCredentials credentials)
|
||||
{
|
||||
var request = new HttpRequestMessage(HttpMethod.Post, Url)
|
||||
{
|
||||
Method = HttpMethod.Post
|
||||
};
|
||||
request.Headers.Add("Authorization", $"AidLogin {credentials.Id}:{credentials.Token}");
|
||||
request.Content = new FormUrlEncodedContent(GetRequestParams(credentials));
|
||||
|
||||
_logger?.LogDebug($"{nameof(RequestReceipt1)}");
|
||||
var response = await HttpClient.SendAsync(request).ConfigureAwait(false);
|
||||
response.EnsureSuccessStatusCode();
|
||||
}
|
||||
|
||||
private async Task<string> RequestReceipt2(GoogleCredentials credentials)
|
||||
{
|
||||
var request = new HttpRequestMessage(HttpMethod.Post, Url)
|
||||
{
|
||||
Method = HttpMethod.Post
|
||||
};
|
||||
request.Headers.Add("Authorization", $"AidLogin {credentials.Id}:{credentials.Token}");
|
||||
var requestParams = GetRequestParams(credentials);
|
||||
requestParams["X-scope"] = $"id{string.Empty}"; // id is always empty here?
|
||||
requestParams["X-kid"] = "|ID|2|";
|
||||
requestParams["X-X-kid"] = "|ID|2|";
|
||||
request.Content = new FormUrlEncodedContent(requestParams);
|
||||
_logger?.LogDebug($"{nameof(RequestReceipt2)}");
|
||||
var response = await HttpClient.SendAsync(request).ConfigureAwait(false);
|
||||
response.EnsureSuccessStatusCode();
|
||||
|
||||
var content = await response.Content.ReadAsStringAsync().ConfigureAwait(false);
|
||||
var result = content.Split(new[] {"|ID|2|:"}, StringSplitOptions.None)[1];
|
||||
if (result == "PHONE_REGISTRATION_ERROR")
|
||||
{
|
||||
throw new InvalidOperationException($"{nameof(RequestReceipt2)} bad response: {result}\n{content}");
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
protected Dictionary<string, string> GetRequestParams(GoogleCredentials credentials)
|
||||
{
|
||||
return new Dictionary<string, string>
|
||||
{
|
||||
{"X-scope", "GCM"},
|
||||
{"X-osv", "23"},
|
||||
{"X-subtype", "54740537194"},
|
||||
{"X-app_ver", "443"},
|
||||
{"X-kid", "|ID|1|"},
|
||||
{"X-appid", _appId},
|
||||
{"X-gmsv", "13283005"},
|
||||
{"X-cliv", "iid-10084000"},
|
||||
{"X-app_ver_name", "51.2 lite"},
|
||||
{"X-X-kid", "|ID|1|"},
|
||||
{"X-subscription", "54740537194"},
|
||||
{"X-X-subscription", "54740537194"},
|
||||
{"X-X-subtype", "54740537194"},
|
||||
{"app", "com.perm.kate_new_6"},
|
||||
{"sender", "54740537194"},
|
||||
{"device", credentials.Id.ToString()},
|
||||
{"cert", "966882ba564c2619d55d0a9afd4327a38c327456"},
|
||||
{"app_ver", "443"},
|
||||
{"info", "g57d5w1C4CcRUO6eTSP7b7VoT8yTYhY"},
|
||||
{"gcm_ver", "13283005"},
|
||||
{"plat", "0"},
|
||||
{"X-messenger2", "1"}
|
||||
};
|
||||
}
|
||||
|
||||
protected const string GcmUserAgent = "Android-GCM/1.5 (generic_x86 KK)";
|
||||
|
||||
protected static Uri Url = new Uri("https://android.clients.google.com/c2dm/register3");
|
||||
}
|
||||
}
|
||||
88
VkNet.TokenMagic/Services/Token/Google/MTalkTcpClient.cs
Normal file
88
VkNet.TokenMagic/Services/Token/Google/MTalkTcpClient.cs
Normal file
|
|
@ -0,0 +1,88 @@
|
|||
using System;
|
||||
using System.Linq;
|
||||
using System.Net.Security;
|
||||
using System.Net.Sockets;
|
||||
using System.Text;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using VkNet.TokenMagic.Models.Google;
|
||||
|
||||
namespace VkNet.TokenMagic.Services.Token.Google
|
||||
{
|
||||
public class MTalkTcpClient
|
||||
{
|
||||
private readonly ILogger<MTalkTcpClient> _logger;
|
||||
|
||||
public MTalkTcpClient(ILogger<MTalkTcpClient> logger)
|
||||
{
|
||||
_logger = logger;
|
||||
}
|
||||
|
||||
public void SendRequest(GoogleCredentials googleCredentials)
|
||||
{
|
||||
var request = BuildMTalkRequest(googleCredentials);
|
||||
_logger?.LogDebug($"{nameof(SendRequest)}");
|
||||
using (var tcpClient = new TcpClient(Host, Port))
|
||||
{
|
||||
using (var sslStream = new SslStream(tcpClient.GetStream(), false, (sender, certificate, chain, errors) => true, null))
|
||||
{
|
||||
sslStream.AuthenticateAsClient(Host);
|
||||
sslStream.Write(request);
|
||||
sslStream.Flush();
|
||||
sslStream.ReadByte();
|
||||
var responseCode = sslStream.ReadByte();
|
||||
if (responseCode != SuccessCode)
|
||||
{
|
||||
throw new InvalidOperationException($"MTalk expected response code [{SuccessCode}], got [{responseCode}]");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private byte[] BuildMTalkRequest(GoogleCredentials googleCredentials)
|
||||
{
|
||||
var idStringBytes = Encoding.ASCII.GetBytes(googleCredentials.Id.ToString());
|
||||
var idLen = VarInt.Write(idStringBytes.Length).ToList();
|
||||
|
||||
var tokenStringByes = Encoding.ASCII.GetBytes(googleCredentials.Token.ToString());
|
||||
var tokenLen = VarInt.Write(tokenStringByes.Length).ToList();
|
||||
|
||||
var hexId = "android-" + BitConverter.ToString(googleCredentials.RawId.ToArray()).Replace("-", string.Empty).ToLowerInvariant();
|
||||
var hexIdBytes = Encoding.ASCII.GetBytes(hexId);
|
||||
var hexIdLen = VarInt.Write(hexIdBytes.Length);
|
||||
|
||||
var body = message1
|
||||
.Concat(idLen)
|
||||
.Concat(idStringBytes)
|
||||
.Concat(message2)
|
||||
.Concat(idLen)
|
||||
.Concat(idStringBytes)
|
||||
.Concat(message3)
|
||||
.Concat(tokenLen)
|
||||
.Concat(tokenStringByes)
|
||||
.Concat(message4)
|
||||
.Concat(hexIdLen)
|
||||
.Concat(hexIdBytes)
|
||||
.Concat(message5)
|
||||
.ToList();
|
||||
|
||||
var bodyLen = VarInt.Write(body.Count);
|
||||
return message6
|
||||
.Concat(bodyLen)
|
||||
.Concat(body)
|
||||
.ToArray();
|
||||
}
|
||||
|
||||
|
||||
|
||||
private byte[] message1 = { 0x0a, 0x0a, 0x61, 0x6e, 0x64, 0x72, 0x6f, 0x69, 0x64, 0x2d, 0x31, 0x39, 0x12, 0x0f, 0x6d, 0x63, 0x73, 0x2e, 0x61, 0x6e, 0x64, 0x72, 0x6f, 0x69, 0x64, 0x2e, 0x63, 0x6f, 0x6d, 0x1a };
|
||||
private byte[] message2 = { 0x22 };
|
||||
private byte[] message3 = { 0x2a };
|
||||
private byte[] message4 = { 0x32 };
|
||||
private byte[] message5 = { 0x42, 0x0b, 0x0a, 0x06, 0x6e, 0x65, 0x77, 0x5f, 0x76, 0x63, 0x12, 0x01, 0x31, 0x60, 0x00, 0x70, 0x01, 0x80, 0x01, 0x02, 0x88, 0x01, 0x01 };
|
||||
private byte[] message6 = { 0x29, 0x02 };
|
||||
|
||||
private const int SuccessCode = 3;
|
||||
private const string Host = "mtalk.google.com";
|
||||
private const int Port = 5228;
|
||||
}
|
||||
}
|
||||
84
VkNet.TokenMagic/Services/Token/Google/ProtobufParser.cs
Normal file
84
VkNet.TokenMagic/Services/Token/Google/ProtobufParser.cs
Normal file
|
|
@ -0,0 +1,84 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using VkNet.TokenMagic.Models.Google;
|
||||
|
||||
namespace VkNet.TokenMagic.Services.Token.Google
|
||||
{
|
||||
public class ProtobufParser
|
||||
{
|
||||
private const int IdFieldNumber = 7;
|
||||
private const int TokenFieldNumber = 8;
|
||||
|
||||
private readonly byte[] data;
|
||||
private int offset;
|
||||
|
||||
public ProtobufParser(byte[] data)
|
||||
{
|
||||
this.data = data;
|
||||
this.offset = 0;
|
||||
}
|
||||
|
||||
public GoogleCredentials Parse()
|
||||
{
|
||||
List<byte> rawId = null;
|
||||
long? id = null;
|
||||
long? token = null;
|
||||
while (offset < data.Length)
|
||||
{
|
||||
var (value, length) = VarInt.Read(data, offset);
|
||||
offset += length;
|
||||
var field = new ProtobufField(value);
|
||||
switch (field.Type)
|
||||
{
|
||||
case 0:
|
||||
var (_, i) = VarInt.Read(data, offset);
|
||||
offset += i;
|
||||
break;
|
||||
case 1 when field.FieldNumber == IdFieldNumber:
|
||||
rawId = data.Skip(offset).Take(8).ToList();
|
||||
id = BitConverter.ToInt64(data, offset);
|
||||
offset += 8;
|
||||
break;
|
||||
case 1 when field.FieldNumber == TokenFieldNumber:
|
||||
token = BitConverter.ToInt64(data, offset);
|
||||
offset += 8;
|
||||
break;
|
||||
case 1:
|
||||
offset += 8;
|
||||
break;
|
||||
case 2:
|
||||
var (skip, j) = VarInt.Read(data, offset);
|
||||
offset += skip + j;
|
||||
break;
|
||||
default:
|
||||
throw new InvalidOperationException($"{nameof(ProtobufParser)} unexpected code [{field.Type}]");
|
||||
}
|
||||
}
|
||||
|
||||
if (offset == data.Length && id == null && token == null)
|
||||
{
|
||||
throw new InvalidOperationException($"{nameof(ProtobufParser)} reached end of data, id and token not found");
|
||||
}
|
||||
|
||||
if (id == null)
|
||||
{
|
||||
throw new InvalidOperationException($"{nameof(ProtobufParser)} id not found");
|
||||
}
|
||||
|
||||
if (token == null)
|
||||
{
|
||||
throw new InvalidOperationException($"{nameof(ProtobufParser)} token not found");
|
||||
}
|
||||
|
||||
return new GoogleCredentials()
|
||||
{
|
||||
Id = id.Value,
|
||||
Token = token.Value,
|
||||
RawId = rawId
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,28 @@
|
|||
using System;
|
||||
using System.Text;
|
||||
|
||||
namespace VkNet.TokenMagic.Services.Token.Google
|
||||
{
|
||||
public class RandomAppIdProvider
|
||||
{
|
||||
public RandomAppIdProvider()
|
||||
{
|
||||
AppId = GenerateRandomString(11);
|
||||
}
|
||||
|
||||
public string AppId { get; }
|
||||
|
||||
private string GenerateRandomString(int length)
|
||||
{
|
||||
var sb = new StringBuilder(length);
|
||||
for (var i = 0; i < length; i++)
|
||||
{
|
||||
sb.Append(Alphabet[_random.Next(Alphabet.Length)]);
|
||||
}
|
||||
return sb.ToString();
|
||||
}
|
||||
|
||||
private readonly Random _random = new Random();
|
||||
private const string Alphabet = "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ_-";
|
||||
}
|
||||
}
|
||||
26
VkNet.TokenMagic/Services/Token/Google/ReceiptReceiver.cs
Normal file
26
VkNet.TokenMagic/Services/Token/Google/ReceiptReceiver.cs
Normal file
|
|
@ -0,0 +1,26 @@
|
|||
using System.Threading.Tasks;
|
||||
|
||||
namespace VkNet.TokenMagic.Services.Token.Google
|
||||
{
|
||||
public class ReceiptReceiver
|
||||
{
|
||||
private readonly MTalkTcpClient _mTalkTcpClient;
|
||||
private readonly AndroidHttpClient _androidHttpClient;
|
||||
private readonly GoogleSecurityHttpClient _googleSecurityHttpClient;
|
||||
|
||||
public ReceiptReceiver(MTalkTcpClient mTalkTcpClient, AndroidHttpClient androidHttpClient, GoogleSecurityHttpClient googleSecurityHttpClient)
|
||||
{
|
||||
_mTalkTcpClient = mTalkTcpClient;
|
||||
_androidHttpClient = androidHttpClient;
|
||||
_googleSecurityHttpClient = googleSecurityHttpClient;
|
||||
}
|
||||
|
||||
public async Task<string> GetReceipt()
|
||||
{
|
||||
var protobuf = await _androidHttpClient.CheckIn().ConfigureAwait(false);
|
||||
var googleCredentials = new ProtobufParser(protobuf).Parse();
|
||||
_mTalkTcpClient.SendRequest(googleCredentials);
|
||||
return await _googleSecurityHttpClient.GetReceipt(googleCredentials).ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
}
|
||||
53
VkNet.TokenMagic/Services/Token/Google/VarInt.cs
Normal file
53
VkNet.TokenMagic/Services/Token/Google/VarInt.cs
Normal file
|
|
@ -0,0 +1,53 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace VkNet.TokenMagic.Services.Token.Google
|
||||
{
|
||||
public static class VarInt
|
||||
{
|
||||
public static (int value, int length) Read(byte[] data, int offset)
|
||||
{
|
||||
var i = 0;
|
||||
var result = 0;
|
||||
while (i + offset < data.Length)
|
||||
{
|
||||
var current = data[i + offset];
|
||||
if ((current & 0x80) != 0)
|
||||
{
|
||||
result |= (current ^ 0x80) << (i * 7);
|
||||
i++;
|
||||
}
|
||||
else
|
||||
{
|
||||
result |= current << (i * 7);
|
||||
i++;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (i + offset == data.Length)
|
||||
{
|
||||
throw new InvalidOperationException($"{nameof(VarInt)} failed to read varint");
|
||||
}
|
||||
|
||||
return (result, i);
|
||||
}
|
||||
|
||||
public static IEnumerable<byte> Write(int value)
|
||||
{
|
||||
while (value != 0)
|
||||
{
|
||||
var current = value & 0x7F;
|
||||
value >>= 7;
|
||||
if (value != 0)
|
||||
{
|
||||
yield return (byte)(current | 0x80);
|
||||
}
|
||||
else
|
||||
{
|
||||
yield return (byte)current;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
12
VkNet.TokenMagic/VkNet.TokenMagic.csproj
Normal file
12
VkNet.TokenMagic/VkNet.TokenMagic.csproj
Normal file
|
|
@ -0,0 +1,12 @@
|
|||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>netstandard2.0</TargetFramework>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.Extensions.Http" Version="2.2.0" />
|
||||
<PackageReference Include="VkNet" Version="1.41.0" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
Loading…
Add table
Add a link
Reference in a new issue