I updated my Instagram API and I read that HttpClient
is now the preferred way in .NET Core 3.1. I would like to get a code review, because there are too many examples and too many ways and I'm not quite sure if I did the migration correctly.
One more question: The reason I added _client.Timeout
is in case the user is using proxy and it is not responding. I think that it's needed only on the first request. Can I somehow restrict it to the first request? Or a better way?
New code:
public class InstagramAPI
{
private static HttpClient _client;
public string Username { get; private set; }
public string Password { get; private set; }
public InstagramAPI(string username, string password, string proxy = null)
{
if (_client == null)
{
var handler = new HttpClientHandler();
if (proxy != null)
{
handler.Proxy = new WebProxy(proxy, true);
}
if (handler.SupportsAutomaticDecompression)
{
handler.AutomaticDecompression = DecompressionMethods.GZip | DecompressionMethods.Deflate;
}
_client = new HttpClient(handler)
{
Timeout = TimeSpan.FromSeconds(10)
};
}
if (string.IsNullOrWhiteSpace(username))
throw new ArgumentException("Username cannot be empty or null.");
if (string.IsNullOrWhiteSpace(password))
throw new ArgumentException("Password cannot be empty or null.");
Username = username;
Password = password;
}
public void Login()
{
try
{
// 1st request
HttpRequestMessage httpRequest = new HttpRequestMessage(HttpMethod.Get, "https://www.instagram.com/");
httpRequest.Headers.Host = "www.instagram.com";
httpRequest.Headers.UserAgent.ParseAdd("Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:78.0) Gecko/20100101 Firefox/78.0");
httpRequest.Headers.Accept.ParseAdd("text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8");
httpRequest.Headers.AcceptLanguage.ParseAdd("en-US");
httpRequest.Headers.AcceptEncoding.ParseAdd("gzip, deflate, br");
httpRequest.Headers.Add("Upgrade-Insecure-Requests", "1");
var getsResponse = _client.SendAsync(httpRequest);
var getResult = getsResponse.Result.Content.ReadAsStringAsync().Result;
string web_key_id = Regex.Match(getResult, "\"key_id\":\"([^\"]*)").Groups[1].Value;
string web_pub_key = Regex.Match(getResult, "\"public_key\":\"([^\"]*)").Groups[1].Value;
string web_key_version = Regex.Match(getResult, "\"version\":\"([^\"]*)").Groups[1].Value;
string csrfToken = Regex.Match(getResult, "\"csrf_token\":\"([^\"]*)").Groups[1].Value;
string rolloutHash = Regex.Match(getResult, "\"rollout_hash\":\"([^\"]*)").Groups[1].Value;
// 2nd request
string encryptedPassword = EncryptPassword(Password, web_pub_key, web_key_id, web_key_version);
HttpRequestMessage httpRequest2 = new HttpRequestMessage(HttpMethod.Post, "https://www.instagram.com/accounts/login/ajax/");
httpRequest2.Content = new FormUrlEncodedContent(new[]
{
new KeyValuePair<string, string>("username", Username),
new KeyValuePair<string, string>("enc_password", encryptedPassword),
new KeyValuePair<string, string>("queryParams", "{}"),
new KeyValuePair<string, string>("optIntoOneTap", "false")
});
httpRequest2.Headers.Host = "www.instagram.com";
httpRequest2.Headers.UserAgent.ParseAdd("Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:78.0) Gecko/20100101 Firefox/78.0");
httpRequest2.Headers.Accept.ParseAdd("*/*");
httpRequest2.Headers.AcceptLanguage.ParseAdd("en-US");
httpRequest2.Headers.AcceptEncoding.ParseAdd("gzip, deflate, br");
httpRequest2.Headers.Add("X-CSRFToken", csrfToken);
httpRequest2.Headers.ConnectionClose = true;
var postResponse = _client.SendAsync(httpRequest2);
var postResult = postResponse.Result.Content.ReadAsStringAsync().Result;
...
}
catch (AggregateException ex)
{
if (ex.InnerException is TaskCanceledException)
{
// return proxy error
MessageBox.Show("We could not connect. It could a proxy problem, if you were using one.", "Connection error", MessageBoxButtons.OK, MessageBoxIcon.Error);
}
}
catch (Exception ex)
{
MessageBox.Show(ex.Message, "Error", MessageBoxButtons.OK, MessageBoxIcon.Error);
}
}
}
Old code:
void Login(string postData)
{
// 1st request
HttpWebRequest request = WebRequest.Create("https://www.instagram.com/") as HttpWebRequest;
request.Method = "GET";
request.AutomaticDecompression = DecompressionMethods.GZip | DecompressionMethods.Deflate;
if (proxyAddress != null)
{
request.Proxy = new WebProxy(proxyAddress);
}
else
{
// The first request takes an abnormally long time: This occurs because WebRequest by default detects and loads proxy settings the first time it starts,
// which can take quite a while.To stop this, simply set the proxy property (WebRequest.Proxy) to null and it'll bypass the check
request.Proxy = null;
}
request.Host = "www.instagram.com";
request.UserAgent = "Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:78.0) Gecko/20100101 Firefox/78.0";
request.Accept = "text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8";
request.Headers.Add("Accept-Language", "en-US");
request.Headers.Add("Accept-Encoding", "gzip, deflate, br");
CookieCollection cookies = new CookieCollection();
IAsyncResult response = request.BeginGetResponse(new AsyncCallback(ReadCallback), request);
// this line implements the timeout, if there is a timeout, the callback fires and the request becomes aborted
ThreadPool.RegisterWaitForSingleObject(response.AsyncWaitHandle, new WaitOrTimerCallback(TimeoutCallback), request, 2000, true);
// The response came in the allowed time. The work processing will happen in the callback function
_manualResetEvent.WaitOne();
if (RequestResult == null)
{
return ResultType.ProxyError;
}
string web_key_id = Regex.Match(RequestResult, "\"key_id\":\"([^\"]*)").Groups[1].Value;
string web_pub_key = Regex.Match(RequestResult, "\"public_key\":\"([^\"]*)").Groups[1].Value;
string web_key_version = Regex.Match(RequestResult, "\"version\":\"([^\"]*)").Groups[1].Value;
string csrfToken = Regex.Match(RequestResult, "\"csrf_token\":\"([^\"]*)").Groups[1].Value;
string rolloutHash = Regex.Match(RequestResult, "\"rollout_hash\":\"([^\"]*)").Groups[1].Value;
// 2nd request
string encryptedPassword = GenerateEncPassword(password, web_pub_key, web_key_id, web_key_version);
byte[] data = Encoding.UTF8.GetBytes(postData);
HttpWebRequest request2 = WebRequest.Create("https://www.instagram.com/accounts/login/ajax/") as HttpWebRequest;
request2.Method = "POST";
request2.AutomaticDecompression = DecompressionMethods.GZip | DecompressionMethods.Deflate;
if (proxyAddress != null)
{
request2.Proxy = new WebProxy(proxyAddress);
}
else
{
request2.Proxy = null;
}
request2.Host = "www.instagram.com";
request2.UserAgent = "Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:78.0) Gecko/20100101 Firefox/78.0";
request2.Accept = "*/*";
request2.ContentType = "application/x-www-form-urlencoded";
request2.Referer = "https://www.instagram.com/";
request2.Headers.Add("Accept-Language", "en-US");
request2.Headers.Add("Accept-Encoding", "gzip, deflate, br");
request2.Headers.Add("X-CSRFToken", csrfToken);
request2.ContentLength = data.Length;
try
{
using (Stream stream = request2.GetRequestStream())
{
stream.Write(data, 0, data.Length);
}
using (HttpWebResponse response2 = request2.GetResponseWithoutException() as HttpWebResponse)
{
using (StreamReader reader = new StreamReader(response2.GetResponseStream()))
{
RequestResult = reader.ReadToEnd();
}
}
}
catch (WebException e)
{
if (e.InnerException is SocketException)
{
return ResultType.ProxyError;
}
return ResultType.ProxyError;
}
...
}
private ManualResetEvent _manualResetEvent = new ManualResetEvent(false);
public string RequestResult { get; private set; }
private void TimeoutCallback(object state, bool timedOut)
{
if (timedOut)
{
HttpWebRequest request = (HttpWebRequest)state;
if (request != null)
{
request.Abort();
}
}
}
private void ReadCallback(IAsyncResult asyncResult)
{
HttpWebRequest request = (HttpWebRequest)asyncResult.AsyncState;
try
{
using (HttpWebResponse response = (HttpWebResponse)request.EndGetResponse(asyncResult))
{
using (StreamReader reader = new StreamReader(response.GetResponseStream()))
{
RequestResult = reader.ReadToEnd();
}
}
}
catch
{
}
_manualResetEvent.Set();
}
```