Как программно авторизоваться в vkontakte (vk.com) на C#

в 15:35, , рубрики: .net, vkonakte, Вконтакте, метки: , , , ,

image

Как-то давно видел на Хабре статью, как авторизоваться в 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=&note_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

Источник

  1. asp.net:

    Этот код не работает по состоянию на сентябрь 2015г.
    даже если передать скрытые поля ip_h, lg_h
    всё равно отправляет на редирект со статусом 4

    parent.onLoginFailed(4, {email: ”});

* - обязательные к заполнению поля


https://ajax.googleapis.com/ajax/libs/jquery/3.4.1/jquery.min.js