mirror of
https://github.com/Rast1234/VkNet.TokenMagic.git
synced 2026-04-28 03:59:28 +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