📜 ⬆️ ⬇️

vkontakte_api: ruby-adapter for VK API

At the beginning of this year I needed to work with the VKontakte API from a rails application. Alas, I did not find any heme that suits me: somewhere I was forced to write method names in camelCase (which looks unnatural in ruby-code), somewhere - I must pass authorization through the library (though I used omniauth ) and in general everywhere for API calls, the hard-core Net::HTTP , blocking the reactor of the event machine I was aiming at. Also in terms of documentation for some reason, everything was very sad, and I had to constantly read the source code.

So vkontakte_api was born. The rail project, which served as a pretext for writing this library, has already managed to rest - but hem lives and continues to grow, in July reaching version 1.0 (which was the reason for significant changes). Using faraday , the library supports calling any API methods, uploading files to VKontakte servers and optional authorization, not accepting the decisions mentioned in the previous paragraph as a programmer.

Let's see how to work with the API using vkontakte_api . As an example, an uncomplicated web application that displays a news feed (API method newsfeed.get ), a list of friends ( friends.get ) and groups ( groups.get ) of a user who has passed OAuth2 authorization will fit. And it will look something like this:
')


Customization


The authorization uses the application ID and the protected key, which can be obtained on the application editing page on VKontakte; and redirect_uri, which will be described later. These parameters are specified in the VkontakteApi.configure block, which is conveniently placed in config/initializers/vkontakte_api.rb ; In the rails application, you can generate this file with default settings using the built-in generator.

 $ rails generate vkontakte_api:install 

Settings are indicated as follows.

 # 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 

(in fact, the available settings are much more , but the rest will not be needed here)

Authorization


You will only need to omniauth via VKontakte, so omniauth will be inappropriate to use omniauth - we use the capabilities of vkontakte_api .

Authorization of the application on VK uses the OAuth2 protocol. This means that as a result of authorization, an access token will be received, which must be transmitted when calling API methods.

The scheme for obtaining it is as follows: the user clicks the link to the authorization page on VK, agrees to give the application access to his (user) data by pressing the “Allow” button, and VKontakte redirect him back to the application by passing the code parameter to the URL. Then the application, using this code, receives the token and user_id user with a separate request and saves them in the session.

To protect against CSRF attacks, OAuth2 recommends transmitting the state parameter with an unanticipated value when sending a user for authorization, having previously saved it in a protected place; and when the user returns, check the state parameter with the saved value.

So, on the login page you need to display a link leading to the application authorization page on VK. vkontakte_api provides the VkontakteApi.authorization_url helper to generate the URL of this page; in the parameters you need to transfer the scope - these are the rights that the application will receive in the form of an array of characters (or strings with names separated by commas) - and the state described above.

 # 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 %> 

Here it should be noted that for unknown reasons, VKontakte ignores state if notify is not specified in scope .

When the user confirms the rights of the application, he will be redirected to the previously specified in the redirect_uri settings (containing the path to SessionsController#callback ), while the state and code parameters will be passed to the URL. As mentioned above, the state needs to be checked against the one already saved; and code stop on code in more detail.

Using the code, you can get an access token, for this you need to fulfill a request to VKontakte. The user is not involved in this request at all - the request goes directly from our server to vk.com. For this vkontakte_api also provides a helper - VkontakteApi.authorize , the only parameter - the notorious 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 

When a user exits our application, simply clean the session:

 class SessionsController < ApplicationController def destroy session[:token] = nil session[:vk_id] = nil redirect_to root_url end end 

Token received, you can work with the API itself.

Calling API methods


To call API methods, you need a VkontakteApi::Client object. In the constructor you just need to pass the token.

Then you can call methods on the client itself. Methods with compound names are called by chain: vk.users.get(params) . In accordance with the conventions adopted in the ruby ​​community, the names of the methods are written in snake_case : the API method likes.getList can be called as vk.likes.get_list .

All API parameters are named and are passed as a hash indexed by parameter names, for example, vk.users.get(uid: 1) . If the API expects to receive a parameter with a collection of objects listed separated by commas, then they can be passed as an array - vkontakte_api stick it together automatically (the scope parameter in the authorization is processed in the same way). In this case, instead of strings, you can use symbols.

So, we need a news feed, friends and groups of the current user. We will also display the user's name and avatar in the navigation. To get this data, there are newsfeed.get , friends.get , groups.get and users.get respectively (the latter will be called by passing the id parameter of our user). The result of newsfeed.get contains separately the news itself, containing the id of users and groups, and separately the arrays with the mentioned users and groups; The MainController#process_feed method, not shown here, adds to each news its source (user or group that wrote a post) under the key 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 

The results of the methods are returned in the form of Hashie::Mash - this is an extension of the standard Hash from the hashie gem , which allows accessing the element through a method whose name corresponds to the key of this element in the hash ( user.name == user[:name] ).

In the navigation you need to show the avatar and the name of the current user, received from VKontakte.

 <%= link_to vk_url(@user), target: '_blank' do %> <%= image_tag(@user.photo, width: 20) %> <%= "#{@user.first_name} #{@user.last_name}" %> <% end %> 

Hereinafter, a number of simple helpers ( vk_url , name_for , avatar_for etc.) are used, defined in the application - they are all quite trivial, if you wish, you can read the code here .

Now we will display on the page a news feed.

 <!-- 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> 

And finally, let's display friends and user groups in the sidebar.

 <!-- 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> 

A live demo can be viewed here (carefully, free heroku). It does not write anything on VKontakte - all API methods are used only to read data and display it on the page. All code lies on Github .

Some more materials on vkontakte_api


Source: https://habr.com/ru/post/151585/


All Articles