Net::HTTP, блокирующий реактор эвентмашины, на которую я тогда прицеливался. Также в плане документации почему-то все было очень грустно, и приходилось постоянно читать исходники.Так появился на свет vkontakte_api. Рельсовый проект, послуживший поводом для написания данной библиотеки, уже успел почить — но гем живет и продолжает развиваться, в июле достигнув версии 1.0 (которая послужила поводом для значительных изменений). Используя
faraday, библиотека поддерживает вызов любых методов API, загрузку файлов на сервера ВКонтакте и опциональную авторизацию, не принимая за программиста решения, упомянутые в предыдущем абзаце.Посмотрим, как работать с API с помощью
vkontakte_api. В качестве примера сгодится несложное веб-приложение, отображающее на странице ленту новостей (API-метод newsfeed.get), список друзей (friends.get) и групп (groups.get) пользователя, прошедшего OAuth2-авторизацию. А выглядеть это будет примерно так:
Настройка
В авторизации используются ID приложения и защищенный ключ, которые можно получить на странице редактирования приложения на ВКонтакте; а также redirect_uri, который будет описан далее. Эти параметры указываются в блоке
VkontakteApi.configure, который удобно разместить в config/initializers/vkontakte_api.rb; в rails-приложении можно сгенерировать этот файл с настройками по умолчанию с помощью встроенного генератора.$ rails generate vkontakte_api:install
Настройки указываются следующим образом.
# config/initializers/vkontakte_api.rb VkontakteApi.configure do |config| config.app_id = '123' # ID приложения config.app_secret = 'AbCdE654' # защищенный ключ config.redirect_uri = 'http://vkontakte-on-rails.herokuapp.com/callback' end
(на самом деле доступных настроек гораздо больше, но остальные тут не понадобятся)
Авторизация
Входить на сайт понадобится только через ВКонтакте, поэтому задействовать
omniauth будет нецелесообразно — используем возможности vkontakte_api.Авторизация приложения на ВКонтакте использует протокол OAuth2. Это означает, что в результате авторизации будет получен токен доступа, который необходимо передавать при вызове методов API.
Схема его получения следующая: пользователь переходит по ссылке на страницу авторизации на ВКонтакте, соглашается дать приложению доступ к его (пользователя) данным, нажимая кнопку «Разрешить», и ВКонтакте редиректит его обратно в приложение, передавая в URL-е параметр
code. Далее приложение, используя этот код, получает токен и user_id пользователя отдельным запросом и сохраняет их в сессии.Для защиты от CSRF-атак протокол OAuth2 рекомендует передавать параметр
state с неугадываемым значением при отправке пользователя на авторизацию, предварительно сохранив его в защищенном месте; а при возвращении пользователя сверять полученный в параметрах state с сохраненным значением.Итак, на странице входа нужно отобразить ссылку, ведущую на страницу авторизации приложения на ВКонтакте.
vkontakte_api предоставляет хелпер VkontakteApi.authorization_url для генерации URL этой страницы; в параметрах нужно передать scope — это права, которые получит приложение, в виде массива символов (или же строки с названиями, разделенными запятыми) — и описанный выше state.# app/controllers/sessions_controller.rb class SessionsController < ApplicationController def new # генерируем случайный state srand session[:state] ||= Digest::MD5.hexdigest(rand.to_s) # и URL страницы авторизации @vk_url = VkontakteApi.authorization_url(scope: [:friends, :groups, :offline, :notify], state: session[:state]) end end
<!-- app/views/sessions/new.html.erb --> <%= link_to @vk_url, class: 'btn btn-primary' do %> <i class="icon-home icon-white"></i> Войти через ВКонтакте <% end %>
Тут нужно заметить, что по непонятным причинам ВКонтакте игнорирует
state, если в scope не указан notify.Когда пользователь подтвердит права приложения, он будет перенаправлен на указанный ранее в настройках
redirect_uri (содержащий путь к SessionsController#callback), при этом в URL будут переданы параметры state и code. Как говорилось чуть выше, state нужно сверить с уже сохраненным; а на code остановимся поподробнее.С помощью кода можно получить токен доступа, для этого нужно выполнить запрос к ВКонтакте. Пользователь в этом запросе никак не участвует — запрос идет прямо от нашего сервера к vk.com. Для этого
vkontakte_api также предоставляет хелпер — VkontakteApi.authorize, единственный параметр — пресловутый code.# encoding: utf-8 class SessionsController < ApplicationController def callback # проверка state if session[:state].present? && session[:state] != params[:state] redirect_to root_url, alert: 'Ошибка авторизации, попробуйте войти еще раз.' and return end # получение токена @vk = VkontakteApi.authorize(code: params[:code]) # и сохранение его в сессии session[:token] = @vk.token # также сохраним id пользователя на ВКонтакте - он тоже пригодится session[:vk_id] = @vk.user_id redirect_to root_url end end
При выходе пользователя из нашего приложения просто почистим сессию:
class SessionsController < ApplicationController def destroy session[:token] = nil session[:vk_id] = nil redirect_to root_url end end
Токен получен, можно работать с самим API.
Вызов методов API
Чтобы вызывать методы API, нужен объект
VkontakteApi::Client. В конструктор нужно просто передать токен.Далее можно вызывать методы на самом клиенте. Методы с составными именами вызываются по цепочке:
vk.users.get(params). В соответствии с принятыми в ruby-сообществе соглашениями названия методов пишутся в snake_case: метод API likes.getList можно вызвать как vk.likes.get_list.Все параметры API являются именованными и передаются в виде хэша, проиндексированного названиями параметров, например
vk.users.get(uid: 1). Если API ожидает получить в параметре коллекцию объектов, перечисленных через запятую, то их можно передать в виде массива — vkontakte_api склеит его автоматически (аналогично обрабатывается параметр scope в авторизации). При этом вместо строк можно использовать символы.Итак, нам нужна лента новостей, друзья и группы текущего пользователя. Также выведем имя и аватар пользователя в навигации. Для получения этих данных есть методы
newsfeed.get, friends.get, groups.get и users.get соответственно (последний будем вызывать, передавая параметром id нашего пользователя). Результат newsfeed.get содержит отдельно сами новости, содержащие id пользователей и групп, и отдельно массивы с упомянутыми пользователями и группами; не показанный здесь метод MainController#process_feed добавляет к каждой новости ее источник (пользователь или группа, написавшая пост) под ключом source.class MainController < ApplicationController def index # сначала создадим клиент API vk = VkontakteApi::Client.new(session[:token]) # теперь получим текущего юзера @user = vk.users.get(uid: session[:vk_id], fields: [:screen_name, :photo]).first # его друзей @friends = vk.friends.get(fields: [:screen_name, :sex, :photo, :last_seen]) # отдельно выберем тех, кто в данный момент онлайн @friends_online = @friends.select { |friend| friend.online == 1 } # группы @groups = vk.groups.get(extended: 1) # первый элемент массива - кол-во групп; его нужно выкинуть @groups.shift # и ленту новостей raw_feed = vk.newsfeed.get(filters: 'post') # обработанную в отдельном методе @newsfeed = process_feed(raw_feed) end end
Результаты методов возвращаются в виде
Hashie::Mash — это расширение стандартного Hash из гема hashie, позволяющее обращаться к элементу через метод, название которого соответствует ключу этого элемента в хэше (user.name == user[:name]).В навигации нужно показать аватар и имя текущего пользователя, полученные с ВКонтакте.
<%= link_to vk_url(@user), target: '_blank' do %> <%= image_tag(@user.photo, width: 20) %> <%= "#{@user.first_name} #{@user.last_name}" %> <% end %>
Здесь и далее используется ряд несложных хелперов (
vk_url, name_for, avatar_for итд), определенных в приложении — все они достаточно тривиальны, при желании можно почитать код здесь.Теперь выведем на страницу ленту новостей.
<!-- app/views/main/index.html.erb --> <% @newsfeed.each do |item| %> <tr> <td> <%= link_to vk_url(item.source), target: '_blank' do %> <%= image_tag avatar_for(item.source) %> <% end %> </td> <td class="wide"> <div class="pull-right"><%= formatted_time_for(item.date) %></div> <%= link_to name_for(item.source), vk_url(item.source), target: '_blank' %> <p><%=raw render_links(item.text) %></p> <% item.attachments.each do |attachment| %> <%= render 'attachment', attachment: attachment %> <% end if item.attachments? %> </td> </tr> <% end %> <!-- app/views/main/_attachment.html.erb --> <p> <% case attachment.type %> <% when 'link' %> <%= link_to attachment.link.title, attachment.link.url, target: '_blank' %> <% when 'photo' %> <%= image_tag attachment.photo.src_big %> <% when 'video' %> <%= image_tag attachment.video.image_big %> <% end %> </p> <div class="clearfix"></div>
И, наконец, отобразим в сайд-баре друзей и группы пользователя.
<!-- app/views/main/_sidebar.html.erb --> <div class="tab-pane active" id="friends_online"> <h6>Друзья онлайн</h6> <%= render 'friends', friends: @friends_online %> </div> <div class="tab-pane" id="friends"> <h6>Все друзья</h6> <%= render 'friends', friends: @friends %> </div> <div class="tab-pane" id="groups"> <h6>Группы</h6> <%= render 'groups' %> </div> <!-- app/views/main/_friends.html.erb --> <table class="table"> <% if friends.empty? %> <tr> <td>Никого не найдено</td> </tr> <% else %> <% friends.each do |friend| %> <tr> <td> <%= link_to image_tag(friend.photo), vk_url(friend), target: '_blank' %> </td> <td class="wide"> <i class="icon-user"></i> <%= link_to "#{friend.first_name} #{friend.last_name}", vk_url(friend), target: '_blank' %> <br /> <%= online_status(friend) %> </td> </tr> <% end %> <% end %> </table> <!-- app/views/main/_groups.html.erb --> <table class="table"> <% if @groups.empty? %> <tr> <td>Вы не состоите в группах</td> </tr> <% else %> <% @groups.each do |group| %> <tr> <td> <%= link_to image_tag(group.photo), vk_url(group), target: '_blank' %> </td> <td class="wide"> <i class="icon-comment"></i> <%= link_to group.name, vk_url(group), target: '_blank' %> </td> </tr> <% end %> <% end %> </table>
Живое демо можно посмотреть здесь (осторожно, бесплатный heroku). Оно ничего не пишет на ВКонтакте — все методы API используются только для чтения данных и вывода их на страницу. Весь код лежит на Github.
Еще немного материалов по vkontakte_api
- домашняя страница проекта
- README.md
- RDoc-документация со 100% покрытием
- более продвинутый пример использования vkontakte_api (асинхронное приложение-мессенджер на вебсокетах)

