Как-то давно видел на Хабре статью, как авторизоваться в yandex. Вдохновившись ею, решил написать аналогичную для «Вконтакте».
Сразу скажу, что в целом подход похожий, но есть и значительные отличия.
В основе всего используются HttpWebRequest и HttpWebResponse.
Шаг 1
Для сокращения кода подключаем:
using System.Collections;
using System.Net;
using System.IO;
using System.Text.RegularExpressions;
using System.Drawing;
Создаем класс, назовем его Net, в нем метод GetHtml:
class Net
{
string remixsid; //Id сессии
public string lastCookies; //Куки
public string GetHtml(string url, string postData) //Возвращает содержимое поданной страницы
{
string HTML = "";
Regex rex1 = new Regex("remixsid=(.*?);", RegexOptions.Multiline | RegexOptions.IgnoreCase | RegexOptions.IgnorePatternWhitespace);
if (url == "0") return "0"; //Проверка на ошибку
HttpWebRequest myHttpWebRequest =(HttpWebRequest)HttpWebRequest.Create(url);
//myHttpWebRequest.Proxy = new WebProxy("127.0.0.1", 8888); //В перспективе можно использовать прокси
if (!String.IsNullOrEmpty(postData)) myHttpWebRequest.Method = "POST";
myHttpWebRequest.Referer = "https://vk.com";
myHttpWebRequest.UserAgent = "Mozila/14.0 (compatible; MSIE 6.0;Windows NT 5.1; SV1; MyIE2;";
myHttpWebRequest.Accept = "image/gif, image/x-xbitmap, image/jpeg,image/pjpeg, application/x-shockwave-flash,application/vnd.ms-excel,application/vnd.ms-powerpoint,application/msword";
myHttpWebRequest.Headers.Add("Accept-Language", "ru");
myHttpWebRequest.ContentType = "application/x-www-form-urlencoded";
myHttpWebRequest.KeepAlive = false;
// передаем Сookie, полученные в предыдущем запросе
if (!String.IsNullOrEmpty(this.remixsid))
{
lastCookies = "remixchk=5;remixsid=" + this.remixsid;
}
if (!String.IsNullOrEmpty(lastCookies))
{
myHttpWebRequest.Headers.Add(System.Net.HttpRequestHeader.Cookie, lastCookies);
}
// ставим False, чтобы при получении кода 302, не делать
// автоматического перенаправления
myHttpWebRequest.AllowAutoRedirect = false;
// передаем параметры
string sQueryString = postData;
byte[] ByteArr = System.Text.Encoding.GetEncoding(1251).GetBytes(sQueryString); //Вконтакте использует кирилическую кодировку
try
{
if (!String.IsNullOrEmpty(postData))
{
myHttpWebRequest.ContentLength = ByteArr.Length;
myHttpWebRequest.GetRequestStream().Write(ByteArr, 0, ByteArr.Length);
};
// делаем запрос
HttpWebResponse myHttpWebResponse = (HttpWebResponse)myHttpWebRequest.GetResponse();
StreamReader myStreamReader;
//Сохраняем Cookie
lastCookies = String.IsNullOrEmpty(myHttpWebResponse.Headers["Set-Cookie"]) ? "" : myHttpWebResponse.Headers["Set-Cookie"];
Match matc1 = rex1.Match(lastCookies);
//Если есть имя сессии, то подменяем Cookie
if (matc1.Groups.Count == 2) { this.remixsid = matc1.Groups[1].ToString(); lastCookies = "remixchk=5;remixsid=" + this.remixsid; }
if (myHttpWebResponse.Headers["Content-Type"].IndexOf("windows-1251") > 0)
{
myStreamReader = new StreamReader(myHttpWebResponse.GetResponseStream(), Encoding.GetEncoding("windows-1251"));
}
else
{
myStreamReader = new StreamReader(myHttpWebResponse.GetResponseStream(), Encoding.UTF8);
}
HTML = myStreamReader.ReadToEnd();
if (HTML == "") //Проверяем на редирект
{
HTML = this.GetHtml(myHttpWebResponse.Headers["Location"].ToString(), "");
}
}
catch (Exception err)
{
//Ошибка в чтении страницы
return "0";
}
return HTML;
}
}
В данном коде есть место, где я подменяю куки. Эту операцию пришлось сделать вынужденно, т.к. если просто загружать куки от предыдущего запроса, то сервер при очередном запросе просто сбрасывал куки. А в такой реализации все работает.
Шаг 2
Чтобы воспользоваться данным методом и авторизоваться, нужно создать объект http и отправить запрос на авторизацию.
Net http = new Net(); //Создаем объект
string post = "email=" + this.login + "&pass=" + this.password + "&q=1&act=login&q=1&al_frame=1&expire=&captcha_sid=&captcha_key=&from_host=vk.com&from_protocol=http&ip_h=4e78766a2890ac1115&quick_expire=1";
string html = http.GetHtml("https://vk.com/", "");
html = http.GetHtml("https://login.vk.com/?act=login", post);
Первый запрос просто получает главную страницу, второй отправляет данные с логином и паролем.
В ответ мы должны получить примерно такой код:
<script type="text/javascript">
var _ua = navigator.userAgent;
var locDomain = 'vk.com'.match(/[a-zA-Z]+.[a-zA-Z]+.?$/)[0];
if (/opera/i.test(_ua) || !/msie 6/i.test(_ua) || document.domain != locDomain) {
document.domain = locDomain;
}
parent.__qlClear();
parent.onLoginDone('/id62983254');
</script>
Шаг 3
Вытягиваем из этого кода Id:
Regex rex4 = new Regex("parent\.onLoginDone\(\'(.*?)\'\)", RegexOptions.Multiline | RegexOptions.IgnoreCase | RegexOptions.IgnorePatternWhitespace);
matc4 = rex4.Match(html);
this.userid = matc4.Groups[1].ToString().Replace("/id", "");
Шаг 4
Прогружаем страничку пользователя и проверяем успешность регистрации:
html = http.GetHtml("https://vk.com/id" + this.userid, "");
int status = Testlogin(html);
status==1 — авторизация прошла успешно
status==2 — аккаунт заблокирован
status==3 — ошибка логина или пароля
status==4 — требуется переавторизация
Код метода проверки на авторизацию и на капчу:
private int Testlogin(string html)
{
if (html.IndexOf("login?act=blocked") > 0) { this.status = 2; return 2; }
if (html.IndexOf("onLoginFailed") > 0) { this.status = 3; return 3; }
Regex rex1 = new Regex("href="\/edit"", RegexOptions.Multiline | RegexOptions.IgnoreCase | RegexOptions.IgnorePatternWhitespace);
Match matc1 = rex1.Match(html);
if (matc1.Groups[0].Length == 0) { this.status = 4; return 4; }
this.status = 1;
return 1;
}
private string TestCaptch(string html)
{
Regex rex1 = new Regex("captcha_sid\":\"(\d*)\"", RegexOptions.Multiline | RegexOptions.IgnoreCase | RegexOptions.IgnorePatternWhitespace);
Match matc1 = rex1.Match(html);
if (matc1.Groups[1].Length == 0) return "0";
return matc1.Groups[1].ToString();
}
Метод TestCaptch возвращает Id капчи, которую можно использовать в дальнейшем разгадывании.
Шаг 5
Разгадываем капчу.
В класс Net добавляем еще один метод:
public Image GetImg(string url, Image image) //Возвращает изображение
{
string postData = "";
string HTML = "";
HttpWebRequest myHttpWebRequest =
(HttpWebRequest)HttpWebRequest.Create(
url);
//myHttpWebRequest.Proxy = new WebProxy("127.0.0.1", 8888);
if (!String.IsNullOrEmpty(postData)) myHttpWebRequest.Method = "POST";
myHttpWebRequest.Referer = "http://vk.com";
myHttpWebRequest.UserAgent = "Mozila/4.0 (compatible; MSIE 6.0;Windows NT 5.1; SV1; MyIE2;";
myHttpWebRequest.Accept = "image/gif, image/x-xbitmap, image/jpeg,image/pjpeg, application/x-shockwave-flash,application/vnd.ms-excel,application/vnd.ms-powerpoint,application/msword";
myHttpWebRequest.Headers.Add("Accept-Language", "ru");
myHttpWebRequest.ContentType = "application/x-www-form-urlencoded";
myHttpWebRequest.KeepAlive = false;
// передаем Cookie, полученные в предыдущем запросе
if (!String.IsNullOrEmpty(this.remixsid))
{
lastCookies = "remixchk=5;remixsid=" + this.remixsid;
}
if (!String.IsNullOrEmpty(lastCookies))
{
myHttpWebRequest.Headers.Add(HttpRequestHeader.Cookie, lastCookies);
}
// ставим False, чтобы при получении кода 302, не делать
// автоматического перенаправления
myHttpWebRequest.AllowAutoRedirect = true;
// передаем параметры
string sQueryString = postData;
byte[] ByteArr = System.Text.Encoding.GetEncoding(1251).GetBytes(sQueryString);
if (!String.IsNullOrEmpty(postData))
{
myHttpWebRequest.ContentLength = ByteArr.Length;
myHttpWebRequest.GetRequestStream().Write(ByteArr, 0, ByteArr.Length);
};
// делаем запрос
try
{
HttpWebResponse myHttpWebResponse = (HttpWebResponse)myHttpWebRequest.GetResponse();
StreamReader myStreamReader;
if (myHttpWebResponse.Headers["Content-Type"].IndexOf("windows-1251") > 0)
{
myStreamReader = new StreamReader(myHttpWebResponse.GetResponseStream(), Encoding.GetEncoding("windows-1251"));
}
else
{
myStreamReader = new StreamReader(myHttpWebResponse.GetResponseStream(), Encoding.UTF8);
}
image = Image.FromStream(myHttpWebResponse.GetResponseStream());
HTML = myStreamReader.ReadToEnd();
myHttpWebResponse.Close();
if (HTML == "") HTML = myHttpWebResponse.Headers["Location"].ToString();
}
catch (Exception err)
{
//Ошибка в чтении страницы
return image;
}
return image;
}
В принципе метод похож на GetHtml, за исключением того, что он возвращает объект Image с запрашиваемой капчей.
Выводим картинку на нашу форму:
public void Addcap(string sid)
{
this.img = "http://vk.com/captcha.php?sid=" + sid;
Image image = net.GetImg(img, pictureBox1.Image);
pictureBox1.Image = image; //Заранее созданный объект PictureBox, в котором будет видна капча
}
Если при отправке какого-нибудь запроса, наша проверка на капчу дала положительный результат, то нам нужно разгадать капчу и отправить запрос заново, но добавить к нему два параметра &captcha_key= и &captcha_sid=
К слову, разгадать капчу можно как в ручную, так и прикрутить код с Antigate.
К примеру, повторный запрос отправки сообщения на стену будет выглядеть так:
post = "act=post&hash=" + hash + "&message=" + textu + "&al=1&captcha_key=" + captcha_key + "&captcha_sid=" + captcha_sid + "&facebook_export=&fixed=&friends_only=&from=¬e_title=&official=&signed=&status_export=&attach1_type=" + attachtype + "&attach1=" + attach + "&to_id=" + this.userid + "&type=all&url=" + System.Web.HttpUtility.UrlEncode(sendurl) + "&title=" + title + "&photo_url=" + System.Web.HttpUtility.UrlEncode(img);
captcha_key — разгаданная капча
captcha_sid — id капчи
sendurl — ссылка в теле сообщения
img — адрес картинки для ссылки
textu — сам текст сообщения
Есть еще параметр Hash, но его мне сгенерить не удалось, потому я достаю его регуляркой, предварительно зайдя на страницу группы.
Regex rex2 = new Regex(""post_hash":"(.*?)"", RegexOptions.Multiline | RegexOptions.IgnoreCase | RegexOptions.IgnorePatternWhitespace);
Match matc2 = rex2.Match(html);
string hash = matc2.Groups[1].ToString();
P.S.
Возможно, кто-то скажет, что логичней было бы использовать API контакта, но, как показала практика, если использовать сразу несколько авторизаций с помощью API c одного IP, то через какое-то время данные аккаунты банятся. А в этом случае мы можем полностью сэмулировать действия реального пользователя через обычный браузер.
Но даже при таком подходе я бы советовал часть запросов, таких как получение информации о пользователях по их Id, выполнять через API, тем более данная операция не требует авторизации.
Если кому-то пригодится, могу в личку скинуть готовый класс под работу с Antigate. Он большой, и к теме напрямую не относится, потому тут выкладывать не стал.
Автор: Violetdrug
Этот код не работает по состоянию на сентябрь 2015г.
даже если передать скрытые поля ip_h, lg_h
всё равно отправляет на редирект со статусом 4
parent.onLoginFailed(4, {email: ”});