SULI COMPANY | Научно-технический сайт Сулико Асабашвили » Программирование » Net » Разработка desktop-приложения для «ВКонтакте» на C#
Информация к новости
  • Просмотров: 1497
  • Автор: sulicompany
  • Дата: 16-12-2012, 17:58
 (голосов: 0)
16-12-2012, 17:58

Разработка desktop-приложения для «ВКонтакте» на C#

Категория: Программирование » Net


Введение

Прежде чем начинать разработку приложения, его необходимо добавить на сайт «ВКонтакте». Сделать это можно на странице:

Рис. 1. Добавление приложения.
Рис. 1. Добавление приложения.

После добавления приложения, вам будет выдан его числовой идентификатор, при помощи которого и будет осуществляться вся дальнейшая работа с приложением.

Рис. 2. Числовой идентификатор в параметрах приложения.
Рис. 2. Числовой идентификатор в параметрах приложения.

Далее нужно ознакомиться с возможностями API и главным образом – с вариантами авторизации пользователей. Сделать это можно на странице:

ВКонтакте предлагает использовать браузер для осуществления авторизации пользователя, в нашем случае речь идет о компонентеWebBrowser. Это сделано для того, чтобы приложение не знало, какой логин и пароль вводит пользователь. Собственно можно обойтись и безWebBrowser, самостоятельно запрашивая логин и пароль у пользователя, если конечно пренебречь седьмым пунктом правил для приложений. Как именно это сделать такжев этой статье, однако вероятней всего, такое приложение не пройдет проверку администрацией ВКонтакте.

Рис. 3. Правила размещения приложений на сайте «ВКонтакте».
 Рис. 3. Правила размещения приложений на сайте «ВКонтакте».

После прохождения пользователем процедуры авторизации, дальнейшую работу с API можно осуществлять посредствам специального ключа (access_token). Запросы нужно будет отправлять на адрес типа: https://api.vkontakte.ru/method/METHOD_NAME?PARAMETERS&access_token=ACCESS_TOKEN

ВКонтакте умеет возвращать результаты выполнения запросов как вJSON-формате, так и вXML. В нашем случае удобней будет запрашивать XML-данные. Для этого нужно к имени метода (METHOD_NAME) добавить расширение xml (METHOD_NAME.xml).

В теории все выглядит достаточно просто, перейдем к практической реализации.

Авторизация

Безопасная авторизация

Как уже было сказано ранее, для авторизации пользователей в desktop-приложениях, ВКонтакте предлагает использовать компонент типаWebBrowser. И не просто предлагает, а категорически запрещает использовать иные методы авторизации. Как говорится, хозяин – барин, и следуя правилам сделаем авторизацию черезWebBrowser.

Для начала создадим новое приложениеWindows Forms.

Примечание.В этой статье я буду использовать .NET Framework 4.0 и язык программирования C#, однако вы смело можете использовать и ранние версии .NET Framework, код должен получиться совместимым.

Разместим на форме компонентWebBrowser.

Рис. 4. Размещение элемента WebBrowser на форме.
Рис. 4. Размещение элемента WebBrowser на форме.

Затем, в событии загрузки формы (Form_Load) откроем вWebBrowserстраницу авторизации пользователя и запросим необходимые права для нашего приложения. В адресе страницы необходимо передать числовой идентификатор нашего приложения (client_id), требуемые разрешения (scope) и указать тип запроса (display).

Идентификатор приложения можно узнать в настройках приложения на сайте ВКонтакте, в моем случае -2419779. Идентификатор приложения лучше всего хранить в отдельной переменной, чтобы можно было легко его поменять. Для этого можно сделать, например, переменнуюappId.

1 privateintappId = 2419779;

Список возможных прав можно найти.

Для удобства мы будем использовать числовые значения (ВКонтакте также позволяет использовать слова) и сделаем перечисление списка прав.

01 privateenumVkontakteScopeList
02 {
03  /// <summary>
04  /// Пользователь разрешил отправлять ему уведомления.
05  /// </summary>
06  notify = 1,
07  /// <summary>
08  /// Доступ к друзьям.
09  /// </summary>
10  friends = 2,
11  /// <summary>
12  /// Доступ к фотографиям.
13  /// </summary>
14  photos = 4,
15  /// <summary>
16  /// Доступ к аудиозаписям.
17  /// </summary>
18  audio = 8,
19  /// <summary>
20  /// Доступ к видеозаписям.
21  /// </summary>
22  video = 16,
23  /// <summary>
24  /// Доступ к предложениям (устаревшие методы).
25  /// </summary>
26  offers = 32,
27  /// <summary>
28  /// Доступ к вопросам (устаревшие методы).
29  /// </summary>
30  questions = 64,
31  /// <summary>
32  /// Доступ к wiki-страницам.
33  /// </summary>
34  pages = 128,
35  /// <summary>
36  /// Добавление ссылки на приложение в меню слева.
37  /// </summary>
38  link = 256,
39  /// <summary>
40  /// Доступ заметкам пользователя.
41  /// </summary>
42  notes = 2048,
43  /// <summary>
44  /// (для Standalone-приложений) Доступ к расширенным методам работы с сообщениями.
45  /// </summary>
46  messages = 4096,
47  /// <summary>
48  /// Доступ к обычным и расширенным методам работы со стеной.
49  /// </summary>
50  wall = 8192,
51  /// <summary>
52  /// Доступ к документам пользователя.
53  /// </summary>
54  docs = 131072
55 }

Необходимые разрешения можно сразу вынести в отдельную переменную. Пускай это будет переменнаяscope.

1 privateintscope = (int)(VkontakteScopeList.audio | VkontakteScopeList.docs | VkontakteScopeList.friends | VkontakteScopeList.link | VkontakteScopeList.messages | VkontakteScopeList.notes | VkontakteScopeList.notify | VkontakteScopeList.offers | VkontakteScopeList.pages | VkontakteScopeList.photos | VkontakteScopeList.questions | VkontakteScopeList.video | VkontakteScopeList.wall);

Конечный код перехода на страницу авторизации ВКонтакте будет выглядеть следующим образом:

1 privatevoidfrmSignin_Load(objectsender, EventArgs e)
2 {
3  webBrowser1.Navigate(String.Format("{0}&scope={1}&display=popup&response_type=token", appId, scope));
4 }

Чтобы посмотреть, что получилось, запустите приложение в режиме отладки (F5).

Важно. Проверьте, чтобы файрвол и/или антивирус не блокировали приложению доступ в интернет.

Если вы не авторизированны на сайте «ВКонтакте», то вWebBrowserзагрузится форма авторизации. В противном случае, вWebBrowserзагрузится страничка с запросом прав для приложения.

Рис. 5. Авторизация на сайте «ВКонтакте» в элементе WebBrowser.
Рис. 5. Авторизация на сайте «ВКонтакте» в элементе WebBrowser.

Процедура авторизации и запрос прав нам необходима для получения ключа доступа (access_token), при помощи которого будет осуществляться дальнейшая работа с API. ВКонтакте выдает ключ на последнем шаге, когда пользователь пройдет все необходимые проверки вWebBrowser. Отследить, что происходит вWebBrowser, можно путем обработки событияDocumentCompleted. Проще всего отслеживать появление словаaccess_tokenв загруженном вWebBrowserадресе, а затем распарситьurlи выдернуть из него идентификатор пользователя и ключ доступа.

01 privatevoidwebBrowser1_DocumentCompleted(objectsender, WebBrowserDocumentCompletedEventArgs e)
02 {
03  if(e.Url.ToString().IndexOf("access_token") != -1)
04  {
05  stringaccessToken = "";
06  intuserId = 0;
07  Regex myReg = newRegex(@"(?<name>[\w\d\x5f]+)=(?<value>[^\x26\s]+)", RegexOptions.IgnoreCase | RegexOptions.Singleline);
08  foreach(Match m inmyReg.Matches(e.Url.ToString()))
09  {
10  if(m.Groups["name"].Value == "access_token")
11  {
12  accessToken = m.Groups["value"].Value;
13  }
14  elseif(m.Groups["name"].Value == "user_id")
15  {
16  userId = Convert.ToInt32(m.Groups["value"].Value);
17  }
18  // еще можно запомнить срок жизни access_token - expires_in,
19  // если нужно
20  }
21  MessageBox.Show(String.Format("Ключ доступа: {0}\nUserID: {1}", accessToken, userId));
22  }
23 }

Примечание. Для использования класса Regex и перечисления RegexOptions необходимо импортировать пространство именSystem.Text.RegularExpressions.

Если теперь запустить программу, то после прохождения процедуры авторизации и запроса прав, появится окошко с ключом доступа и идентификатором пользователя.

Рис. 6. Результат работы программы.
Рис. 6. Результат работы программы.

Авторизация для реальных пацанов

Всё, что делается пользователем через браузер - можно сделать программно. В этой части статьи мы пройдем процедуру авторизации и получение ключа доступа (access_token) без использованияWebBrowser. Но я еще раз хочу обратить ваше внимание на то, что это запрещено правилами ВКонтакте и приложение, использующее подобный метод авторизации, скорей всего не пройдет проверку и будет запрещено администрацией ВКонтакте.

Прежде чем что-то начинать делать, нужно разобраться, как это вообще работает. Для этого придется анализировать трафик, который проходит при авторизации и добавлении разрешений пользователем в браузере. Посмотреть трафик можно при помощи локального прокси-сервера -.

Но опять же, нестись сломя голову изучать трафик не стоит, ибо API ВКонтакте позволяет работать с различными типами устройств и наверняка можно получить максимально простой и понятный трафик от него. Страница авторизации принимает параметрdisplay, который позволяет указать тип окна авторизации в зависимости от устройства, на котором работает приложение.Displayможет иметь следующие значения:page,popup,touchиwap. В случае с «» мы использовалиpopup, что вполне оптимально. При программной имитации действия пользователя, разумней будет использовать, напримерwap, т.к. очевидно, что выдаваемый html-код будет оптимизирован под мобильные телефоны, т.е. довольно простым.

Итак, запустимFiddler2и браузер –(Fiddler к нему цепляется автоматически, без каких-либо дополнительных настроек).

Примечание.Если вы авторизированны на сайте ВКонтакте, то выйдите, чтобы иметь возможность отследить весь цикл взаимодействия браузера с сайтом.

В адресную строкуInternet Explorerвставим ссылку запроса прав для приложения: http://api.vkontakte.ru/oauth/authorize?client_id=2419779&scope=1&display=wap&response_type=token

Примечание.В параметр client_id укажите идентификатор вашего приложения. Остальные параметры оставьте без изменений.

Пройдем весь цикл, от авторизации до установки разрешений, и посмотрим, что у нас будет в отчётеFiddler2.

Рис. 7. Просмотр журнала в Fiddler2.
Рис. 7. Просмотр журнала в Fiddler2.

Первый запрос идет к странице, адрес которой мы вставили в адресную строку браузера. На ней мы указали наш логин и пароль. Далее, на скрине выше, под номерами: 2, 3 и 4 идет мусор, на который можно не обращать внимание. После нажатия на кнопку «Войти» данные с формы авторизации отправляются на страницу: https://login.vk.com/?act=login&soft=1&utf8=1

 Рис. 8. Отправленные (сверху) и полученые (снизу) HTTP-заголовки при авторизации на сайте «ВКонтакте».
 Рис. 8. Отправленные (сверху) и полученые (снизу) HTTP-заголовки при авторизации на сайте «ВКонтакте».

После чего сервер выдает– перенаправление на страницу запроса разрешений у пользователя для нашего приложения, а также куки (Cookies) авторизации.

После нажатия пользователем на кнопку «Разрешить», форма отправляется на страницу типа: http://api.vkontakte.ru/oauth/grant_access?hash=68af114762009eaf68&client_id=2419779&settings=1&redirect_uri=blank.html&response_type=token&state=

Где опять происходит перенаправление с 302-ым HTTP-кодом на страницу содержащую ключ доступа, идентификатор пользователя и срок годности ключа.

Ну что ж, дело за малым - повторить все эти действия программно. ВместоWebBrowserмы будем использовать классыHttpWebRequestиHttpWebResponse. По аналогии с «», создадим две локальные переменные –appIdиscope, а также перечислениеVkontakteScopeList.

Примечание.Переменные и перечисление лучше всего сделать один раз в static классе, чтобы они были доступны в рамках всего приложения.

Также создадим форму с двумя текстовыми полями и одной кнопкой. Имя первого текстового поля будетtbLogin, а второго –tbPassword. Кнопку назовемbtnSignin, весь последующий код будет писаться в обработчике нажатия кнопки (btnSignin_Click).

Рис. 9. Форма авторизации.
Рис. 9. Форма авторизации.

Как и в случае с браузером, первым шагом будет обращение к странице авторизации и запроса прав для приложения.

1 HttpWebRequest myReq = (HttpWebRequest)HttpWebRequest.Create(String.Format("{0}&scope={1}&display=wap&response_type=token", appId, scope));
2 HttpWebResponse myResp = (HttpWebResponse)myReq.GetResponse();
3 StreamReader myStream = newStreamReader(myResp.GetResponseStream(), Encoding.UTF8);
4 stringhtml = myStream.ReadToEnd();

Примечание.Для использования классов HttpWebRequest и HttpWebResponse необходимо импортировать (при помощи директивыusing) пространство имен System.Net. Для использования класса StreamReader, требуется пространство имен System.IO. КлассEncoding находится в пространстве имен System.Text, которое также необходимо импортировать.

Содержимое страницы будет помещено в переменнуюhtml. Нам нужно найти на этой странице форму авторизации и передать в следующем запросе все её элементы, указав логин и пароль пользователя. Проще всего сначала найти форму.

1 Regex myReg = newRegex("<form(.*?)>(?<form_body>.*?)</form>", RegexOptions.IgnoreCase | RegexOptions.Multiline | RegexOptions.Singleline);
2 if(!myReg.IsMatch(html) || (html = myReg.Match(html).Groups["form_body"].Value) == "")
3 {
4  MessageBox.Show("Не удалось получить форму авторизации. Проверьте шаблон регулярного выражения.");
5  return;
6 }

Примечание. Для использования класса Regex и перечисления RegexOptions необходимо импортировать пространство именSystem.Text.RegularExpressions.

Поиск формы происходит с использованием регулярных выражений. В случае успеха, содержимое формы будет помещено в переменнуюhtml. Затем нам нужно выдернуть из формы все элементы и сформировать из них параметры для следующего запроса, подставив логин и пароль пользователя.

01 myReg = newRegex("<input(.*?)name=\"(?<name>[^\x22]+)\"(.*?)((value=\"(?<value>[^\x22]*)\"(.*?))|(.?))>", RegexOptions.IgnoreCase | RegexOptions.Multiline | RegexOptions.Singleline);
02 NameValueCollection qs = newNameValueCollection();
03 foreach(Match m inmyReg.Matches(html))
04 {
05  stringval = m.Groups["value"].Value;
06  if(m.Groups["name"].Value == "email")
07  {
08  val = tbLogin.Text;
09  }
10  elseif(m.Groups["name"].Value == "pass")
11  {
12  val = tbPassword.Text;
13  }
14  qs.Add(m.Groups["name"].Value, HttpUtility.UrlEncode(val));
15 }

Примечание. Здесь для кодирования параметров url используется класс HttpUtility, который находится в сборке System.Web.dll. Вам необходимо . Если вы используете Visual Studio 2010 и клиентский профиль .NET Framework 4, вам может потребоваться , иначе сборки System.Web.dll может не быть в списке доступных сборок. После подключения сборки, необходимо импортировать пространство имен System.Web.
Класс NameValueCollection принадлежит пространству имен System.Collections.Specialized, которое также необходимо импортировать
.

Все найденные элементы формы перебираются циклом и помещаются в коллекциюqsбез изменений. Вместо логина (email) и пароля (pass) подставляется данные текущего пользователя из текстовых полей –tbLoginиtbPassword.

Затем данные нужно отправить на страницу https://login.vk.com/?act=login&soft=1&utf8=1 методомPOST. Здесь важно правильно указатьContentType, и отключить автоматическое перенаправление (AllowAutoRedirect). Также в запросе нужно создатьCookieContainer, чтобы иметь возможность работать с куками (Cookies).

01 byte[] b = System.Text.Encoding.UTF8.GetBytes(String.Join("&", from item inqs.AllKeys select item + "="+ qs[item]));
02
03 myReq = (HttpWebRequest)HttpWebRequest.Create("");
04 myReq.CookieContainer = newCookieContainer();
05 myReq.Method = "POST";
06 myReq.ContentType = "application/x-www-form-urlencoded";
07 myReq.ContentLength = b.Length;
08 myReq.GetRequestStream().Write(b, 0, b.Length);
09 myReq.AllowAutoRedirect = false;
10
11 myResp = (HttpWebResponse)myReq.GetResponse();

Примечание.Для корректной работы конструкции from ... in ... select необходимо импортировать пространство имен System.Linq.

Никакого html-содержимого в ответе сервера не будет. При успешном прохождении авторизации, сервер вернет куки (Cookies). Куки мы запишем в отдельную переменную, для последующего использования.

1 CookieContainer cc = newCookieContainer();
2 foreach(Cookie c inmyResp.Cookies)
3 {
4  cc.Add(c);
5 }

Также сервер вернет ссылку на страницу запроса у пользователя разрешений для нашего приложения. Адрес страницы должен находиться в HTTP-заголовкеLocation. Мы сделаем перенаправление вручную методомGET, при этом важно не забыть передать полученные куки (Cookies), иначе сервер опять возвратит страницу авторизации.

01 if(!String.IsNullOrEmpty(myResp.Headers["Location"]))
02 {
03  // делаем редирект
04  myReq = (HttpWebRequest)HttpWebRequest.Create(myResp.Headers["Location"]);
05  myReq.CookieContainer = cc;// передаем куки
06  myReq.Method = "GET";
07  myReq.ContentType = "text/html";
08
09  myResp = (HttpWebResponse)myReq.GetResponse();
10  myStream = newStreamReader(myResp.GetResponseStream(), Encoding.UTF8);
11  html = myStream.ReadToEnd();
12 }
13 else
14 {
15  // что-то пошло не так
16  MessageBox.Show("Ошибка. Ожидался редирект.");
17  return;
18 }

В переменнойhtmlдолжно быть содержимое страницы запроса разрешений для приложения. Чтобы проверить так это или нет, мы попытаемся найти в теле страницы имя и ссылку пользователя. Делать мы это будем, как и положено реальным пацанам, при помощи регулярных выражений. Если имя пользователя не будет найдено, значит процедура авторизации прошла неудачно.

1 myReg = newRegex("Вы авторизованы как <b><a href=\"(?<url>[^\\x22]+)\">(?<user>[^\\x3c]+)</a></b>", RegexOptions.IgnoreCase | RegexOptions.Multiline | RegexOptions.Singleline);
2 if(!myReg.IsMatch(html))
3 {
4  MessageBox.Show("Ошибка: Авторизация не прошла.");
5  return;
6 }
7
8 MessageBox.Show(String.Format("Авторизация успешно прошла.\nПользователь {0}", myReg.Match(html).Groups["user"].Value));

Затем нужно получить форму, чтобы в последующем её отправить. Но в отличие от первой формы (формы авторизации), в форме запроса разрешений для приложения нас будет интересовать в первую очередь адрес, на который она будет отправлена. Собственно больше ничего в этой форме нам и не нужно, однако если в ней в будущем появятся дополнительные важные элементы, их придется парсить и передавать в запросе.

1 myReg = newRegex("<form(.*?)action=\"(?<post_url>[^\\x22]+)\"(.*?)>(?<form_body>.*?)</form>", RegexOptions.IgnoreCase | RegexOptions.Multiline | RegexOptions.Singleline);
2 if(!myReg.IsMatch(html))
3 {
4  MessageBox.Show("Не удалось получить форму. Проверьте шаблон регулярного выражения.");
5  return;
6 }

Полученный адрес может быть относительным, поэтому его нужно немного дописать.

1 stringurl = myReg.Match(html).Groups["post_url"].Value;
2 if(!url.ToLower().StartsWith("")) { url = String.Format("{0}", url); }

Вот и все, делаем последний запрос.

1 myReq = (HttpWebRequest)HttpWebRequest.Create(url);
2 myReq.CookieContainer = cc; // не забываем передавать куки
3 myReq.Method = "POST";
4 myReq.ContentType = "application/x-www-form-urlencoded";
5 myReq.AllowAutoRedirect = false;
6
7 myResp = (HttpWebResponse)myReq.GetResponse();

И выдергиваем ключ доступа из адреса, на который перенаправляет нас сервер.

01 if(!String.IsNullOrEmpty(myResp.Headers["Location"]))
02 {
03  myReg = newRegex(@"(?<name>[\w\d\x5f]+)=(?<value>[^\x26\s]+)", RegexOptions.IgnoreCase | RegexOptions.Singleline);
04  foreach(Match m inmyReg.Matches(myResp.Headers["Location"]))
05  {
06  if(m.Groups["name"].Value == "access_token")
07  {
08  accessToken = m.Groups["value"].Value;
09  }
10  elseif(m.Groups["name"].Value == "user_id")
11  {
12  userId = m.Groups["value"].Value;
13  }
14  // еще можно запомнить срок жизни access_token - expires_in,
15  // если нужно
16  }
17  MessageBox.Show(String.Format("Ключ доступа: {0}\nUserID: {1}", accessToken, userId));
18 }
19 else
20 {
21  MessageBox.Show("Ошибка. Ожидался редирект.");
22 }

В итоге получилось то же самое, что и при «», только без участия пользователя, а может быть даже и без его ведома.

Работа с API

Имея на руках ключ доступа, можно выполнять любые запросы к API (конечно, при условии, что от пользователя были получены необходимые права для приложения). Поскольку вся работа с API, по сути, ограничивается обращением к странице типа: https://api.vkontakte.ru/method/METHOD_NAME.xml?PARAMETERS&access_token=ACCESS_TOKEN то разумней всего сделать одну функцию, которая будет выполнять запросы к API и возвращать результат в XML. Эта функция должна принимать имя api-метода, а также дополнительные параметры, в зависимости от запроса. Так как в разных api-методах может быть разное количество параметров, для их передачи можно использовать коллекцию типаNameValueCollection.

1 privateXmlDocument ExecuteCommand(stringname, NameValueCollection qs)
2 {
3  XmlDocument result = newXmlDocument();
4  result.Load(String.Format("{0}.xml?access_token={1}&{2}", name, accessToken, String.Join("&", from item inqs.AllKeys select item + "="+ qs[item])));
5  returnresult;
6 }

Для примера, можно запросить у ВКонтакте, при помощи созданной функции, детальную информацию о пользователе.

1 NameValueCollection qs = newNameValueCollection();
2 qs["uid"] = userId.ToString();
3 MessageBox.Show(ExecuteCommand("getProfiles", qs).InnerXml);

Полный список api-методов ВКонтакте можно.

Реальный пример

Для удобства работы с API ВКонтакте можно сделать отдельный класс, напримерVKAPI. В перспективе, этот класс должен содержать все функции и методы, которые есть в API. Но мы этого делать не будем, по крайней мере, я точно не буду, а вот вы вполне можете совершить подвиг.
Поскольку для выполнения запросов нам понадобитсяaccess_token, то его можно хранить в нашем классе и принимать в конструкторе.