vkontakte 0.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
data/Gemfile ADDED
@@ -0,0 +1,13 @@
1
+ source "http://rubygems.org"
2
+
3
+ gemspec
4
+
5
+ gem "activesupport"
6
+ gem "i18n"
7
+
8
+ gem "rspec", "~> 2.6.0", :require => "spec"
9
+ gem "fakeweb"
10
+
11
+ # To use debugger (ruby-debug for Ruby 1.8.7+, ruby-debug19 for Ruby 1.9.2+)
12
+ # gem 'ruby-debug'
13
+ # gem 'ruby-debug19'
@@ -0,0 +1,20 @@
1
+ Copyright 2011 Aimbulance
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining
4
+ a copy of this software and associated documentation files (the
5
+ "Software"), to deal in the Software without restriction, including
6
+ without limitation the rights to use, copy, modify, merge, publish,
7
+ distribute, sublicense, and/or sell copies of the Software, and to
8
+ permit persons to whom the Software is furnished to do so, subject to
9
+ the following conditions:
10
+
11
+ The above copyright notice and this permission notice shall be
12
+ included in all copies or substantial portions of the Software.
13
+
14
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
@@ -0,0 +1,51 @@
1
+ = Vkontakte
2
+
3
+ The easiest way to access Vkontakte API and some other utils.
4
+ More info about API:
5
+
6
+ 1. http://vkontakte.ru/developers.php?oid=-1&p=%D0%90%D0%B2%D1%82%D0%BE%D1%80%D0%B8%D0%B7%D0%B0%D1%86%D0%B8%D1%8F
7
+
8
+ 2. http://vkontakte.ru/developers.php?oid=-1&p=%D0%92%D1%8B%D0%BF%D0%BE%D0%BB%D0%BD%D0%B5%D0%BD%D0%B8%D0%B5_%D0%B7%D0%B0%D0%BF%D1%80%D0%BE%D1%81%D0%BE%D0%B2_%D0%BA_API
9
+
10
+ == Install
11
+
12
+ gem 'vkontakte'
13
+
14
+ == Configure
15
+
16
+ Vkontakte.setup do |config|
17
+ config.app_id = "YOUR Vkontakte API ID"
18
+ config.app_secret = "YOUR Vkontakte APP SECRET"
19
+ config.format = :json
20
+ config.debug = false
21
+ config.logger = File.open(Rails.root.join('log', 'vkontakte.log'), "a")
22
+ end
23
+
24
+ == Usage
25
+
26
+ === Secure API
27
+
28
+ @app = Vkontakte::App::Secure.new
29
+ @app.secure.getAppBalance # {"response"=>2000}
30
+ @app.auth # {"expires_in"=>0, "access_token"=>"d173f5...319f"}
31
+
32
+ === IFrame application
33
+
34
+ Check if auth_key is valid:
35
+
36
+ def index
37
+ @app = Vkontakte::App::Iframe.new
38
+ @app.params = params
39
+
40
+ if @app.valid_auth_key?
41
+ session[:viewer_id] = params[:viewer_id]
42
+ else
43
+ render :action => "failure"
44
+ end
45
+ end
46
+
47
+ == Test
48
+
49
+ rake spec
50
+
51
+ Copyright (c) 2011 Aimbulance, released under the MIT license
@@ -0,0 +1,25 @@
1
+ # encoding: UTF-8
2
+ require 'rubygems'
3
+ begin
4
+ require 'bundler/setup'
5
+ rescue LoadError
6
+ puts 'You must `gem install bundler` and `bundle install` to run rake tasks'
7
+ end
8
+
9
+ require 'rake'
10
+ require 'rdoc/task'
11
+
12
+ require 'rspec/core'
13
+ require 'rspec/core/rake_task'
14
+
15
+ RSpec::Core::RakeTask.new(:spec)
16
+
17
+ task :default => :spec
18
+
19
+ Rake::RDocTask.new(:rdoc) do |rdoc|
20
+ rdoc.rdoc_dir = 'rdoc'
21
+ rdoc.title = 'ShareChecker'
22
+ rdoc.options << '--line-numbers' << '--inline-source'
23
+ rdoc.rdoc_files.include('README.rdoc')
24
+ rdoc.rdoc_files.include('lib/**/*.rb')
25
+ end
@@ -0,0 +1,29 @@
1
+ require 'active_support/core_ext/module/attribute_accessors'
2
+ require 'active_support/core_ext/object'
3
+ require 'active_support/core_ext/string'
4
+
5
+ module Vkontakte
6
+ autoload :Config, 'vkontakte/config'
7
+ autoload :Utils, 'vkontakte/utils'
8
+
9
+ module App
10
+ autoload :Base, 'vkontakte/app/base'
11
+ autoload :Iframe, 'vkontakte/app/iframe'
12
+ autoload :Secure, 'vkontakte/app/secure'
13
+ end
14
+
15
+ module Api
16
+ autoload :Base, 'vkontakte/api/base'
17
+ autoload :Photos, 'vkontakte/api/photos'
18
+ autoload :Friends, 'vkontakte/api/friends'
19
+ autoload :Groups, 'vkontakte/api/groups'
20
+ autoload :Secure, 'vkontakte/api/secure'
21
+ end
22
+
23
+ mattr_accessor :config
24
+ @@config = Config.new
25
+
26
+ def self.setup(&block)
27
+ yield config
28
+ end
29
+ end
@@ -0,0 +1,13 @@
1
+ module Vkontakte
2
+ module Api
3
+ class Base
4
+ attr_accessor :app
5
+
6
+ delegate :call, :to => :app
7
+
8
+ def initialize(base)
9
+ @app = base
10
+ end
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,42 @@
1
+ module Vkontakte
2
+ module Api
3
+ module Friends
4
+
5
+ def self.included(base)
6
+ base.class_eval do
7
+ define_method :friends do
8
+ @friends ||= Standart.new(self)
9
+ end
10
+ end
11
+ end
12
+
13
+ class Standart < Api::Base
14
+ # Возвращает список идентификаторов друзей пользователя или
15
+ # расширенную информацию о друзьях пользователя (при использовании параметра fields).
16
+ # http://vkontakte.ru/developers.php?oid=-1&p=friends.get
17
+ #
18
+ def get(options = {})
19
+ call('friends.get', options)
20
+ end
21
+
22
+ # Возвращает список идентификаторов друзей текущего пользователя, которые установили данное приложение.
23
+ #
24
+ def getAppUsers(options = {})
25
+ call('friends.getAppUsers', options)
26
+ end
27
+
28
+ # Возвращает список идентификаторов, находящихся на сайте друзей, текущего пользователя.
29
+ #
30
+ def getOnline(options = {})
31
+ call('friends.getOnline', options)
32
+ end
33
+
34
+ # Возвращает список идентификаторов общих друзей между парой пользователей.
35
+ #
36
+ def getMutual(options = {})
37
+ call('friends.getMutual', options)
38
+ end
39
+ end
40
+ end
41
+ end
42
+ end
@@ -0,0 +1,41 @@
1
+ module Vkontakte
2
+ module Api
3
+ module Groups
4
+
5
+ def self.included(base)
6
+ base.class_eval do
7
+ define_method :groups do
8
+ @groups ||= Standart.new(self)
9
+ end
10
+ end
11
+ end
12
+
13
+ class Standart < Api::Base
14
+ # Возвращает список групп указанного пользователя.
15
+ # http://vkontakte.ru/developers.php?oid=-1&p=groups.get
16
+ #
17
+ def get(options = {})
18
+ call('groups.get', options)
19
+ end
20
+
21
+ # Возвращает информацию о заданной группе или о нескольких группах.
22
+ #
23
+ def getById(options = {})
24
+ call('groups.getById', options)
25
+ end
26
+
27
+ # Возвращает информацию о том является ли пользователь участником заданной группы.
28
+ #
29
+ def isMember(options = {})
30
+ call('groups.isMember', options)
31
+ end
32
+
33
+ # Возвращает список участников группы.
34
+ #
35
+ def getMembers(options = {})
36
+ call('groups.getMembers', options)
37
+ end
38
+ end
39
+ end
40
+ end
41
+ end
@@ -0,0 +1,218 @@
1
+ module Vkontakte
2
+ module Api
3
+ module Photos
4
+
5
+ def self.included(base)
6
+ base.class_eval do
7
+ define_method :photos do
8
+ @photos ||= Standart.new(self)
9
+ end
10
+ end
11
+ end
12
+
13
+ module Extended
14
+ def self.included(base)
15
+ base.define_method :photos do
16
+ @photos ||= Extend.new(self)
17
+ end
18
+ end
19
+ end
20
+
21
+ class Standart < Api::Base
22
+
23
+ # возвращает список альбомов пользователя.
24
+ #
25
+ def getAlbums(options={})
26
+ call('photos.getAlbums', options)
27
+ end
28
+
29
+ # возвращает количество альбомов пользователя.
30
+ #
31
+ def getAlbumsCount(options={})
32
+ call('photos.getAlbumsCount', options)
33
+ end
34
+
35
+ # возвращает список фотографий в альбоме.
36
+ #
37
+ def get(options={})
38
+ call('photos.get', options)
39
+ end
40
+
41
+ # возвращает все фотографии пользователя в антихронологическом порядке.
42
+ #
43
+ def getAll(options={})
44
+ call('photos.getAll', options)
45
+ end
46
+
47
+ # возвращает информацию о фотографиях.
48
+ #
49
+ def getById(options={})
50
+ call('photos.getById', options)
51
+ end
52
+
53
+ # создает пустой альбом для фотографий.
54
+ #
55
+ def createAlbum(options={})
56
+ call('photos.createAlbum', options)
57
+ end
58
+
59
+ # обновляет данные альбома для фотографий.
60
+ #
61
+ def editAlbum(options={})
62
+ call('photos.editAlbum', options)
63
+ end
64
+
65
+ # переносит фотографию из одного альбома в другой.
66
+ #
67
+ def move(options={})
68
+ call('photos.move', options)
69
+ end
70
+
71
+ # делает фотографию обложкой альбома.
72
+ #
73
+ def makeCover(options={})
74
+ call('photos.makeCover', options)
75
+ end
76
+
77
+ # меняет порядок альбома в списке альбомов пользователя.
78
+ #
79
+ def reorderAlbums(options={})
80
+ call('photos.reorderAlbums', options)
81
+ end
82
+
83
+ # меняет порядок фотографий в списке фотографий альбома.
84
+ #
85
+ def reorderPhotos(options={})
86
+ call('photos.reorderPhotos', options)
87
+ end
88
+
89
+ # возвращает адрес сервера для загрузки фотографий.
90
+ #
91
+ def getUploadServer(options={})
92
+ call('photos.getUploadServer', options)
93
+ end
94
+
95
+ # сохраняет фотографии после успешной загрузки.
96
+ #
97
+ def save(options={})
98
+ call('photos.save', options)
99
+ end
100
+
101
+ # возвращает адрес сервера для загрузки фотографии на страницу пользователя.
102
+ #
103
+ def getProfileUploadServer(options={})
104
+ call('photos.getProfileUploadServer', options)
105
+ end
106
+
107
+ # сохраняет фотографию страницы пользователя после успешной загрузки.
108
+ #
109
+ def saveProfilePhoto(options={})
110
+ call('photos.saveProfilePhoto', options)
111
+ end
112
+
113
+ # возвращает адрес сервера для загрузки фотографии в специальный альбом, предназначенный для фотографий со стены.
114
+ #
115
+ def getWallUploadServer(options={})
116
+ call('photos.getWallUploadServer', options)
117
+ end
118
+
119
+ # сохраняет фотографию после успешной загрузки.
120
+ #
121
+ def saveWallPhoto(options={})
122
+ call('photos.saveWallPhoto', options)
123
+ end
124
+ end
125
+
126
+
127
+
128
+
129
+ class Extend < Standart
130
+ # возвращает список комментариев к фотографии.
131
+ #
132
+ def getComments(options={})
133
+ call('photos.getComments', options)
134
+ end
135
+
136
+ # возвращает список комментариев к альбому или ко всем альбомам.
137
+ #
138
+ def getAllComments(options={})
139
+ call('photos.getAllComments', options)
140
+ end
141
+
142
+ # создает новый комментарий к фотографии.
143
+ #
144
+ def createComment(options={})
145
+ call('photos.createComment', options)
146
+ end
147
+
148
+ # изменяет текст комментария к фотографии.
149
+ #
150
+ def editComment(options={})
151
+ call('photos.editComment', options)
152
+ end
153
+
154
+ # удаляет комментарий к фотографии.
155
+ #
156
+ def deleteComment(options={})
157
+ call('photos.deleteComment', options)
158
+ end
159
+
160
+ # восстанавливает комментарий к фотографии.
161
+ #
162
+ def restoreComment(options={})
163
+ call('photos.restoreComment', options)
164
+ end
165
+
166
+ # возвращает список фотографий, на которых отмечен пользователь.
167
+ #
168
+ def getUserPhotos(options={})
169
+ call('photos.getUserPhotos', options)
170
+ end
171
+
172
+ # возвращает список отметок на фотографии.
173
+ #
174
+ def getTags(options={})
175
+ call('photos.getTags', options)
176
+ end
177
+
178
+ # добавляет отметку на фотографию.
179
+ #
180
+ def putTag(options={})
181
+ call('photos.putTag', options)
182
+ end
183
+
184
+ # удаляет отметку с фотографии.
185
+ #
186
+ def removeTag(options={})
187
+ call('photos.removeTag', options)
188
+ end
189
+
190
+ # удаляет фотоальбом пользователя.
191
+ #
192
+ def deleteAlbumnew(options={})
193
+ call('photos.deleteAlbumnew', options)
194
+ end
195
+
196
+ # возвращает адрес сервера для загрузки фотографии в качестве прикрепления к личному сообщению.
197
+ #
198
+ def getMessagesUploadServer(options={})
199
+ call('photos.getMessagesUploadServer', options)
200
+ end
201
+
202
+ # сохраняет фотографию после загрузки.
203
+ #
204
+ def saveMessagesPhoto(options={})
205
+ call('photos.saveMessagesPhoto', options)
206
+ end
207
+
208
+ # удаляет фотографию.
209
+ #
210
+ def delete(options={})
211
+ call('photos.delete', options)
212
+ end
213
+ end
214
+
215
+
216
+ end
217
+ end
218
+ end
@@ -0,0 +1,77 @@
1
+ module Vkontakte
2
+ module Api
3
+ module Secure
4
+
5
+ def self.included(base)
6
+ base.class_eval do
7
+ define_method :secure do
8
+ @secure ||= Standart.new(self)
9
+ end
10
+ end
11
+ end
12
+
13
+ class Standart < Api::Base
14
+
15
+ def default_options
16
+ {
17
+ :timestamp => Time.now.utc.to_i,
18
+ :random => Kernel.rand(10000),
19
+ :client_secret => Vkontakte.config.app_secret
20
+ }
21
+ end
22
+
23
+ # Отправляет уведомление пользователю.
24
+ # http://vkontakte.ru/developers.php?oid=-1&p=secure.sendNotification
25
+ #
26
+ def sendNotification(options = {})
27
+ options = default_options.merge(options)
28
+ call('secure.sendNotification', options)
29
+ end
30
+
31
+ # Возвращает платежный баланс (счет) приложения в сотых долях голоса.
32
+ #
33
+ def getAppBalance(options = {})
34
+ options = default_options.merge(options)
35
+ call("secure.getAppBalance", options)
36
+ end
37
+
38
+ # Возвращает баланс пользователя на счету приложения в сотых долях голоса.
39
+ #
40
+ def getBalance(options = {})
41
+ options = default_options.merge(options)
42
+ call("secure.getBalance", options)
43
+ end
44
+
45
+ # Списывает голоса со счета пользователя на счет приложения (в сотых долях).
46
+ #
47
+ def withdrawVotes(options = {})
48
+ options = default_options.merge(options)
49
+ call("secure.withdrawVotes", options)
50
+ end
51
+
52
+ # Выводит историю транзакций по переводу голосов между пользователями и приложением.
53
+ #
54
+ def getTransactionsHistory(options = {})
55
+ options = default_options.merge(options)
56
+ call("secure.getTransactionsHistory", options)
57
+ end
58
+
59
+ # Поднимает пользователю рейтинг от имени приложения.
60
+ #
61
+ def addRating(options = {})
62
+ options = default_options.merge(options)
63
+ call("secure.addRating", options)
64
+ end
65
+
66
+ # Устанавливает счетчик, который выводится пользователю жирным шрифтом в левом меню.
67
+ # Это происходит только в том случае, если пользователь добавил приложение в левое меню со страницы приложения,
68
+ # списка приложений или настроек.
69
+ #
70
+ def setCounter(options = {})
71
+ options = default_options.merge(options)
72
+ call("secure.setCounter", options)
73
+ end
74
+ end
75
+ end
76
+ end
77
+ end
@@ -0,0 +1,112 @@
1
+ require 'httparty'
2
+
3
+ module Vkontakte
4
+ module App
5
+ class Base
6
+ include ::HTTParty
7
+
8
+ base_uri "https://api.vkontakte.ru"
9
+ format Vkontakte.config.format
10
+ debug_output Vkontakte.config.logger
11
+
12
+ attr_accessor :auth
13
+
14
+ def initialize(app_id = nil, app_secret = nil)
15
+ @config = {
16
+ :app_id => (app_id || Vkontakte.config.app_id),
17
+ :app_secret => (app_secret || Vkontakte.config.app_secret)
18
+ }
19
+ end
20
+
21
+ # http://vkontakte.ru/developers.php?oid=-1&p=%D0%90%D0%B2%D1%82%D0%BE%D1%80%D0%B8%D0%B7%D0%B0%D1%86%D0%B8%D1%8F
22
+ # Site auth:
23
+ # https://api.vkontakte.ru/oauth/access_token?
24
+ # client_id=APP_ID&
25
+ # client_secret=APP_SECRET&
26
+ # code=7a6fa4dff77a228eeda56603b8f53806c883f011c40b72630bb50df056f6479e52a
27
+ #
28
+ # Server auth:
29
+ # https://api.vkontakte.ru/oauth/access_token?
30
+ # client_id=' + APP_ID + '&client_secret=' + APP_SECRET + '&grant_type=client_credentials'
31
+ #
32
+ # Response:
33
+ # {"access_token":"533bacf01e11f55b536a565b57531ac114461ae8736d6506a3", "expires_in":43200, "user_id":6492}
34
+ #
35
+ def authorize(code = nil, options = {})
36
+ options = {
37
+ :client_id => @config[:app_id],
38
+ :client_secret => @config[:app_secret],
39
+ :code => code
40
+ }.merge(options)
41
+
42
+ # Server auth
43
+ if options[:code].blank?
44
+ options.delete(:code)
45
+ options[:grant_type] = 'client_credentials'
46
+ end
47
+
48
+ @auth = get("/oauth/access_token", options)
49
+ end
50
+
51
+ # Check if app is authorized
52
+ #
53
+ def authorized?
54
+ auth && auth['access_token']
55
+ end
56
+
57
+ # Выполнение запросов к API
58
+ # https://api.vkontakte.ru/method/METHOD_NAME?PARAMETERS&access_token=ACCESS_TOKEN
59
+ # METHOD_NAME – название метода из списка функций API,
60
+ # PARAMETERS – параметры соответствующего метода API,
61
+ # ACCESS_TOKEN – ключ доступа, полученный в результате успешной авторизации приложения.
62
+ # Example:
63
+ # https://api.vkontakte.ru/method/getProfiles?uid=66748&access_token=533bacf01e11f55b536a565b57531ac114461ae8736d6506a3
64
+ #
65
+ # More info: http://vkontakte.ru/developers.php?oid=-1&p=%D0%92%D1%8B%D0%BF%D0%BE%D0%BB%D0%BD%D0%B5%D0%BD%D0%B8%D0%B5_%D0%B7%D0%B0%D0%BF%D1%80%D0%BE%D1%81%D0%BE%D0%B2_%D0%BA_API
66
+ #
67
+ def call(method_name, params = {})
68
+ params[:access_token] ||= @auth['access_token'] if authorized?
69
+
70
+ unless params[:access_token].blank?
71
+ get("/method/#{method_name}", params)
72
+ else
73
+ raise VkException.new(method_name, {
74
+ :error => 'access_token is blank',
75
+ :error_description => 'You need first authorize app before call API methods.'
76
+ })
77
+ end
78
+ end
79
+
80
+ protected
81
+
82
+ def get(method_name, options = {})
83
+ response = self.class.get(method_name, :query => options)
84
+
85
+ if response['error']
86
+ raise VkException.new(method_name, response)
87
+ else
88
+ return response
89
+ end
90
+ end
91
+ end
92
+
93
+ # Errors
94
+ # {"error":"invalid_grant","error_description":"Code is expired."}
95
+ # {"error":{"error_code":5,"error_msg":"User authorization failed: invalid application type","request_params":[{"key":"oauth","value":"1"},{"key":"method","value":"getProfiles"},{"key":"uid","value":"66748"},{"key":"access_token","value":"533bacf01e11f55b536a565b57531ac114461ae8736d6506a3"}]}}
96
+ #
97
+ class VkException < Exception
98
+ def initialize(method_name, options)
99
+ error_hash = options.symbolize_keys
100
+ @message = "Error in #{method_name}: "
101
+
102
+ if error_hash[:error].is_a?(Hash)
103
+ @message += error_hash[:error].inspect
104
+ else
105
+ @message += [error_hash[:error], error_hash[:error_description]].join('-')
106
+ end
107
+
108
+ super @message
109
+ end
110
+ end
111
+ end
112
+ end
@@ -0,0 +1,71 @@
1
+ module Vkontakte
2
+ module App
3
+ # IFrame приложения
4
+ # More info at http://vkontakte.ru/developers.php?id=-1_27971896&s=1
5
+ #
6
+ class Iframe < Base
7
+ include Api::Photos
8
+ include Api::Friends
9
+ include Api::Groups
10
+
11
+ attr_accessor :params
12
+
13
+ # Основные параметры запуска приложения
14
+ # При отображении приложения посредством flashVars или строки запроса (для IFrame приложений)
15
+ # в него передаются следующие параметры: api_url, api_id, user_id, sid, secret, group_id ...
16
+ # http://vkontakte.ru/developers.php?id=-1_27971896&s=1
17
+ #
18
+ def params=(value)
19
+ @params = value.symbolize_keys
20
+
21
+ if @params[:access_token] && auth.nil?
22
+ self.auth = { 'access_token' => @params[:access_token] }
23
+ end
24
+
25
+ @params
26
+ end
27
+
28
+ # Этот параметр приходит, если в приложении включена система платежей (во вкладке Платежи при редактировании приложения).
29
+ # auth_key вычисляется на сервере ВКонтакте следующим образом:
30
+ # auth_key = md5(api_id + '_' + viewer_id + '_' + api_secret)
31
+ #
32
+ def valid_auth_key?
33
+ !params[:auth_key].blank? && params[:auth_key] == auth_key
34
+ end
35
+
36
+ # Переменная language может принимать следующие значения:
37
+ #
38
+ # 0 – русский язык.
39
+ # 1 – украинский язык.
40
+ # 2 – белорусский язык.
41
+ # 3 – английский язык.
42
+ #
43
+ def language
44
+ return :ru if params[:language].blank?
45
+ case params[:language].to_s
46
+ when '0' then :ru
47
+ when '1' then :uk
48
+ when '2' then :be
49
+ when '3' then :en
50
+ end
51
+ end
52
+
53
+ # результат выполнения API-запроса, формирующийся при просмотре приложения.
54
+ # Параметры этого запроса можно ввести в разделе редактирования приложения.
55
+ # Например, для получения информации об указанных пользователях, можно использовать следующий запрос:
56
+ # method=getProfiles&uids={user_id},{viewer_id},1,6492&format=json&v=2.0
57
+ #
58
+ def api_result
59
+ @api_result ||= MultiJson.decode(params[:api_result])
60
+ end
61
+
62
+ protected
63
+
64
+ # это ключ, необходимый для авторизации пользователя на стороннем сервере
65
+ #
66
+ def auth_key
67
+ Utils.md5( [@config[:app_id], params[:viewer_id], @config[:app_secret]].join('_') )
68
+ end
69
+ end
70
+ end
71
+ end
@@ -0,0 +1,15 @@
1
+ module Vkontakte
2
+ module App
3
+ # Данный метод доступен только с серверной стороны.
4
+ # More info at http://vkontakte.ru/developers.php?id=-1_27971896&s=1
5
+ #
6
+ class Secure < Base
7
+ include Api::Secure
8
+
9
+ def initialize(app_id = nil, app_secret = nil)
10
+ super
11
+ authorize
12
+ end
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,38 @@
1
+ module Vkontakte
2
+ class Config < Hash
3
+ # Creates an accessor that simply sets and reads a key in the hash:
4
+ #
5
+ # class Config < Hash
6
+ # hash_accessor :routes, :secret_key, :service_number, :project_name
7
+ # end
8
+ #
9
+ # config = Config.new
10
+ # config.routes = '/posts/message'
11
+ # config[:routes] #=> '/posts/message'
12
+ #
13
+ def self.hash_accessor(*names) #:nodoc:
14
+ names.each do |name|
15
+ class_eval <<-METHOD, __FILE__, __LINE__ + 1
16
+ def #{name}
17
+ self[:#{name}]
18
+ end
19
+
20
+ def #{name}=(value)
21
+ self[:#{name}] = value
22
+ end
23
+ METHOD
24
+ end
25
+ end
26
+
27
+ hash_accessor :app_id, :app_secret, :debug, :logger, :format
28
+
29
+ def initialize(other={})
30
+ merge!(other)
31
+ self[:app_id] ||= "Vkontakte API ID"
32
+ self[:app_secret] ||= "Vkontakte APP SECRET"
33
+ self[:format] ||= :json
34
+ self[:debug] ||= false
35
+ self[:logger] ||= nil
36
+ end
37
+ end
38
+ end
@@ -0,0 +1,24 @@
1
+ require 'digest/md5'
2
+
3
+ module Vkontakte
4
+ module Utils
5
+ extend self
6
+
7
+ def generate_cookie_sign(data, app_secret)
8
+ md5(collect_params(data) + app_secret)
9
+ end
10
+
11
+ def generate_api_sign(cookies, data)
12
+ cookies['secret'] ||= Vkontakte.config.app_secret
13
+ md5(cookies['mid'], collect_params(data), cookies['secret'])
14
+ end
15
+
16
+ def collect_params(data)
17
+ data.sort{|a, b| a.first.to_s <=> b.first.to_s}.collect{|key, value| "#{key}=#{value}"}.join
18
+ end
19
+
20
+ def md5(*args)
21
+ Digest::MD5.hexdigest(args.join)
22
+ end
23
+ end
24
+ end
@@ -0,0 +1,3 @@
1
+ module Vkontakte
2
+ VERSION = "0.0.1".freeze
3
+ end
@@ -0,0 +1,56 @@
1
+ require "spec_helper"
2
+
3
+ describe Vkontakte::Api::Friends do
4
+ it "should be valid" do
5
+ Vkontakte::Api::Friends.should be_a(Module)
6
+ end
7
+
8
+ context "iframe" do
9
+ before(:each) do
10
+ @token = '3a3d250e705051b03ed479343c3ec2833783eea3eea29860182716ed1d40319'
11
+ @iframe = Vkontakte::App::Iframe.new
12
+ @iframe.auth = {'access_token' => @token}
13
+ end
14
+
15
+ it "should get friends list by uid param" do
16
+ FakeWeb.register_uri(:get,
17
+ "https://api.vkontakte.ru/method/friends.get?access_token=#{@token}&uid=81202312",
18
+ :body => '{"response":[2592709,3859793,4663468]}')
19
+
20
+ @iframe.friends.get(:uid => 81202312).should == {"response"=>[2592709, 3859793, 4663468]}
21
+ end
22
+
23
+ it "should get friends list by fields" do
24
+ FakeWeb.register_uri(:get,
25
+ "https://api.vkontakte.ru/method/friends.get?access_token=#{@token}&fields=81202312",
26
+ :body => '{"response":[{"uid":2592709,"first_name":"Павел","last_name":"Галета","online":0},{"uid":3859793,"first_name":"Мария","last_name":"Семёнова","online":0},{"uid":4663468,"first_name":"Ekaterina","last_name":"Koronotova","online":0}]}')
27
+
28
+ @iframe.friends.get(:fields => 81202312).should == {"response"=>[{"uid"=>2592709, "last_name"=>"\320\223\320\260\320\273\320\265\321\202\320\260", "online"=>0, "first_name"=>"\320\237\320\260\320\262\320\265\320\273"}, {"uid"=>3859793, "last_name"=>"\320\241\320\265\320\274\321\221\320\275\320\276\320\262\320\260", "online"=>0, "first_name"=>"\320\234\320\260\321\200\320\270\321\217"}, {"uid"=>4663468, "last_name"=>"Koronotova", "online"=>0, "first_name"=>"Ekaterina"}]}
29
+ end
30
+
31
+ it "should get getAppUsers" do
32
+ FakeWeb.register_uri(:get,
33
+ "https://api.vkontakte.ru/method/friends.getAppUsers?access_token=#{@token}",
34
+ :body => '{"response":[2592709]}')
35
+
36
+ @iframe.friends.getAppUsers.should == {"response" => [2592709]}
37
+ end
38
+
39
+ it "should get getOnline" do
40
+ FakeWeb.register_uri(:get,
41
+ "https://api.vkontakte.ru/method/friends.getOnline?access_token=#{@token}&uid=2592709",
42
+ :body => '{"response":[2450999,2488708,2649518,4440077]}')
43
+
44
+ @iframe.friends.getOnline(:uid => 2592709).should == {"response"=>[2450999, 2488708, 2649518, 4440077]}
45
+ end
46
+
47
+ it "should get getMutual" do
48
+ FakeWeb.register_uri(:get,
49
+ "https://api.vkontakte.ru/method/friends.getMutual?target_uid=2450999&access_token=#{@token}&source_uid=2592709",
50
+ :body => '{"response":[2301578,2619312,5818827,6391852,6411298,6422462]}')
51
+
52
+ response = @iframe.friends.getMutual(:target_uid => 2450999, :source_uid => 2592709)
53
+ response.should == {"response" => [2301578,2619312,5818827,6391852,6411298,6422462]}
54
+ end
55
+ end
56
+ end
@@ -0,0 +1,64 @@
1
+ require "spec_helper"
2
+
3
+ describe Vkontakte::Api::Groups do
4
+ it "should be valid" do
5
+ Vkontakte::Api::Groups.should be_a(Module)
6
+ end
7
+
8
+ context "iframe" do
9
+ before(:each) do
10
+ @token = '3a3d250e705051b03ed479343c3ec2833783eea3eea29860182716ed1d40319'
11
+ @iframe = Vkontakte::App::Iframe.new
12
+ @iframe.auth = {'access_token' => @token}
13
+ end
14
+
15
+ it "should get groups list by uid param" do
16
+ FakeWeb.register_uri(:get,
17
+ "https://api.vkontakte.ru/method/groups.get?access_token=#{@token}&uid=81202312",
18
+ :body => '{"response":[16527885]}')
19
+
20
+ @iframe.groups.get(:uid => 81202312).should == {"response" => [16527885]}
21
+ end
22
+
23
+ it "should raise permission error on access friend groups" do
24
+ FakeWeb.register_uri(:get,
25
+ "https://api.vkontakte.ru/method/groups.get?access_token=#{@token}&uid=2592709",
26
+ :body => '{"error":{"error_code":7,"error_msg":"Permission to perform this action is denied by user","request_params":[{"key":"oauth","value":"1"},{"key":"method","value":"groups.get"},{"key":"uid","value":"2592709"},{"key":"access_token","value":"74aee6063ec4aea07047ba3cb47079607f870797079ea90fef75c6361570a5f"}]}}')
27
+
28
+ lambda {
29
+ @iframe.groups.get(:uid => 2592709)
30
+ }.should raise_error Vkontakte::App::VkException
31
+ end
32
+
33
+ context "exists group" do
34
+ before(:each) do
35
+ @group_id = 16527885
36
+ end
37
+
38
+ it "should return group info" do
39
+ FakeWeb.register_uri(:get,
40
+ "https://api.vkontakte.ru/method/groups.getById?access_token=#{@token}&gid=#{@group_id}",
41
+ :body => '{"response":[{"gid":16527885,"name":"Club Music Group of Kiev","screen_name":"club16527885","is_closed":0,"type":"group","photo":"http:\/\/cs884.vkontakte.ru\/g16527885\/c_08b73308.jpg","photo_medium":"http:\/\/cs884.vkontakte.ru\/g16527885\/b_6e68053d.jpg","photo_big":"http:\/\/cs884.vkontakte.ru\/g16527885\/a_ba67625c.jpg"}]}')
42
+
43
+ @iframe.groups.getById(:gid => @group_id).should == {"response"=>[{"photo"=>"http://cs884.vkontakte.ru/g16527885/c_08b73308.jpg", "name"=>"Club Music Group of Kiev", "gid"=>16527885, "is_closed"=>0, "photo_medium"=>"http://cs884.vkontakte.ru/g16527885/b_6e68053d.jpg", "type"=>"group", "photo_big"=>"http://cs884.vkontakte.ru/g16527885/a_ba67625c.jpg", "screen_name"=>"club16527885"}]}
44
+ end
45
+
46
+ it "should return group member" do
47
+ FakeWeb.register_uri(:get,
48
+ "https://api.vkontakte.ru/method/groups.isMember?access_token=#{@token}&gid=#{@group_id}&uid=81202312",
49
+ :body => '{"response":1}')
50
+
51
+ @iframe.groups.isMember(:uid => 81202312, :gid => @group_id).should == {"response" => 1}
52
+ end
53
+
54
+ it "should return all groups members" do
55
+ FakeWeb.register_uri(:get,
56
+ "https://api.vkontakte.ru/method/groups.getMembers?access_token=#{@token}&gid=#{@group_id}&count=5",
57
+ :body => '{"response":{"count":29364,"users":[107765962,66506999,145557591,72256631,28639402]}}')
58
+
59
+ response = @iframe.groups.getMembers(:gid => @group_id, :count => 5)
60
+ response.should == {"response"=>{"count"=>29364, "users"=>[107765962, 66506999, 145557591, 72256631, 28639402]}}
61
+ end
62
+ end
63
+ end
64
+ end
@@ -0,0 +1,27 @@
1
+ require "spec_helper"
2
+
3
+ describe Vkontakte::Api::Photos do
4
+ it "should be valid" do
5
+ Vkontakte::Api::Photos.should be_a(Module)
6
+ end
7
+
8
+ context "params" do
9
+ before(:each) do
10
+ @token = '3a3d250e705051b03ed479343c3ec2833783eea3eea29860182716ed1d40319'
11
+ @iframe = Vkontakte::App::Iframe.new
12
+ @iframe.auth = {'access_token' => @token}
13
+ end
14
+
15
+ it "should be call getAlbums method" do
16
+ response = '{"response":[{"aid":"17071606","thumb_id":"98054577","owner_id":"6492","title":"",
17
+ "description":"","created":"1204576880","updated":"1229532461", "size":"3","privacy":"0"}]}'
18
+
19
+ FakeWeb.register_uri(:get,
20
+ "https://api.vkontakte.ru/method/photos.getAlbums?access_token=#{@token}",
21
+ :body => response)
22
+
23
+ @iframe.photos.getAlbums.should == {"response" => [{"aid" => "17071606","thumb_id" => "98054577","owner_id" => "6492","title" => "", "description" => "","created" => "1204576880","updated" => "1229532461", "size" => "3","privacy" => "0"}]}
24
+ end
25
+
26
+ end
27
+ end
@@ -0,0 +1,88 @@
1
+ require "spec_helper"
2
+
3
+ describe Vkontakte::Api::Secure do
4
+ it "should be valid" do
5
+ Vkontakte::Api::Secure.should be_a(Module)
6
+ end
7
+
8
+ context "secure" do
9
+ before(:all) do
10
+ @token = '3a3d250e705051b03ed479343c3ec2833783eea3eea29860182716ed1d40319'
11
+
12
+ FakeWeb.register_uri(:get,
13
+ "https://api.vkontakte.ru/oauth/access_token?grant_type=client_credentials&client_id=#{Vkontakte.config.app_id}&client_secret=#{Vkontakte.config.app_secret}",
14
+ :body => '{"access_token":"#{@token}"}')
15
+
16
+ @app = Vkontakte::App::Secure.new
17
+ @options = @app.secure.default_options.merge(:access_token => @token)
18
+ end
19
+
20
+ it "should call sendNotification" do
21
+ p = @options.merge(:uids => 81202312, :message => "test")
22
+
23
+ FakeWeb.register_uri(:get,
24
+ "https://api.vkontakte.ru/method/secure.sendNotification?" + HTTParty::HashConversions.to_params(p),
25
+ :body => '{"response":"81202312"}')
26
+
27
+ @app.secure.sendNotification(p).should == {"response"=>"81202312"}
28
+ end
29
+
30
+ it "should call getAppBalance" do
31
+ FakeWeb.register_uri(:get,
32
+ "https://api.vkontakte.ru/method/secure.getAppBalance?" + HTTParty::HashConversions.to_params(@options),
33
+ :body => '{"response":5000}')
34
+
35
+ @app.secure.getAppBalance(@options).should == {"response"=>5000}
36
+ end
37
+
38
+ it "should call getBalance" do
39
+ p = @options.merge(:uid => 81202312)
40
+
41
+ FakeWeb.register_uri(:get,
42
+ "https://api.vkontakte.ru/method/secure.getBalance?" + HTTParty::HashConversions.to_params(p),
43
+ :body => '{"response":350}')
44
+
45
+ @app.secure.getBalance(p).should == {"response"=>350}
46
+ end
47
+
48
+ it "should call withdrawVotes" do
49
+ p = @options.merge(:uid => 81202312, :votes => 2)
50
+
51
+ FakeWeb.register_uri(:get,
52
+ "https://api.vkontakte.ru/method/secure.withdrawVotes?" + HTTParty::HashConversions.to_params(p),
53
+ :body => '{"response":2}')
54
+
55
+ @app.secure.withdrawVotes(p).should == {"response"=>2}
56
+ end
57
+
58
+ it "should call getTransactionsHistory" do
59
+ p = @options.merge(:uid => 81202312, :type => 0, :limit => 2)
60
+
61
+ FakeWeb.register_uri(:get,
62
+ "https://api.vkontakte.ru/method/secure.getTransactionsHistory?" + HTTParty::HashConversions.to_params(p),
63
+ :body => '{"response":[{"id":"65968","uid_from":"34804733","uid_to":"33239732","votes":"1000","date":"1243421339"},{"id":"65956","uid_from":"35003049","uid_to":"33239732","votes":"300","date":"1243421213"}]}')
64
+
65
+ @app.secure.getTransactionsHistory(p).should == {"response"=>[{"votes"=>"1000", "uid_to"=>"33239732", "date"=>"1243421339", "id"=>"65968", "uid_from"=>"34804733"}, {"votes"=>"300", "uid_to"=>"33239732", "date"=>"1243421213", "id"=>"65956", "uid_from"=>"35003049"}]}
66
+ end
67
+
68
+ it "should call addRating" do
69
+ p = @options.merge(:uid => 81202312, :rate => 200)
70
+
71
+ FakeWeb.register_uri(:get,
72
+ "https://api.vkontakte.ru/method/secure.addRating?" + HTTParty::HashConversions.to_params(p),
73
+ :body => '{"response":{"rating_added":200}}')
74
+
75
+ @app.secure.addRating(p).should == {"response" => {"rating_added" => 200}}
76
+ end
77
+
78
+ it "should call setCounter" do
79
+ p = @options.merge(:uid => 81202312, :counter => 4)
80
+
81
+ FakeWeb.register_uri(:get,
82
+ "https://api.vkontakte.ru/method/secure.setCounter?" + HTTParty::HashConversions.to_params(p),
83
+ :body => '{"response":1}')
84
+
85
+ @app.secure.setCounter(p).should == {"response" => 1}
86
+ end
87
+ end
88
+ end
@@ -0,0 +1,5 @@
1
+ require "spec_helper"
2
+
3
+ describe Vkontakte::App::Base do
4
+
5
+ end
@@ -0,0 +1,32 @@
1
+ require "spec_helper"
2
+
3
+ describe Vkontakte::App::Iframe do
4
+ it "should be valid" do
5
+ Vkontakte::App::Iframe.should be_a(Module)
6
+ end
7
+
8
+ context "params" do
9
+ before(:each) do
10
+ @params = {"referrer"=>"profile", "is_app_user"=>"0", "api_settings"=>"0", "parent_language"=>"0", "hash"=>"", "sid"=>"db5aa091133cb1971817421f3cf14a1b78605ad2ab67218e8066c2bd57e736", "language"=>"0", "group_id"=>"0", "user_id"=>"2592709", "viewer_type"=>"1", "secret"=>"492aeb00af", "lc_name"=>"5941f805", "access_token"=>"3a3d250e705051b03ed479343c3ec2833783eea3eea29860182716ed1d40319", "viewer_id"=>"81202312", "api_url"=>"http://api.vkontakte.ru/api.php"}
11
+ @params["auth_key"] = Vkontakte::Utils.md5([Vkontakte.config.app_id, @params['viewer_id'], Vkontakte.config.app_secret].join('_'))
12
+ @params["api_result"] = '{"response":[{"uid":81202312,"first_name":"Tester","last_name":"Tester"}]}'
13
+ @params["app_id"] = Vkontakte.config.app_id
14
+
15
+ @iframe = Vkontakte::App::Iframe.new
16
+ @iframe.params = @params
17
+ end
18
+
19
+ it "should parse request params" do
20
+ @iframe.language.should == :ru
21
+ @iframe.auth.should_not be_nil
22
+ @iframe.auth['access_token'].should == @params["access_token"]
23
+ @iframe.valid_auth_key?.should be_true
24
+ @iframe.api_result.should == {"response"=>[{"uid"=>81202312, "last_name"=>"Tester", "first_name"=>"Tester"}]}
25
+ end
26
+
27
+ it "should not be valid auth_key" do
28
+ @iframe.params = @params.merge("auth_key" => 'wrong')
29
+ @iframe.valid_auth_key?.should_not be_true
30
+ end
31
+ end
32
+ end
@@ -0,0 +1,35 @@
1
+ require "rubygems"
2
+ require "bundler/setup"
3
+
4
+ $:.push File.expand_path(File.join(File.dirname(__FILE__), '..', 'lib'))
5
+ require "vkontakte"
6
+ require "fakeweb"
7
+
8
+ # Load support files
9
+ Dir["#{File.dirname(__FILE__)}/support/**/*.rb"].each { |f| require f }
10
+
11
+ Vkontakte.setup do |config|
12
+ config.app_id = 10000
13
+ config.app_secret = "supersecret"
14
+ config.format = :json
15
+ config.debug = false
16
+ config.logger = nil
17
+ end
18
+
19
+ RSpec.configure do |config|
20
+ # Remove this line if you don't want RSpec's should and should_not
21
+ # methods or matchers
22
+ require 'rspec/expectations'
23
+ config.include RSpec::Matchers
24
+
25
+ # == Mock Framework
26
+ config.mock_with :rspec
27
+
28
+ config.before(:suite) do
29
+ FakeWeb.allow_net_connect = false
30
+ end
31
+
32
+ config.after(:suite) do
33
+ FakeWeb.allow_net_connect = true
34
+ end
35
+ end
@@ -0,0 +1,31 @@
1
+ require 'spec_helper'
2
+
3
+ describe Vkontakte do
4
+ it "should be valid" do
5
+ Vkontakte.should be_a(Module)
6
+ end
7
+
8
+ context "setup" do
9
+ before(:each) do
10
+ Vkontakte.setup do |config|
11
+ config.app_id = 1234567
12
+ config.app_secret = "supersecretkey"
13
+ config.format = :xml
14
+ config.debug = false
15
+ config.logger = nil
16
+ end
17
+ end
18
+
19
+ it "should set config options" do
20
+ Vkontakte.config.app_id.should == 1234567
21
+ Vkontakte.config.app_secret.should == 'supersecretkey'
22
+ Vkontakte.config.format.should == :xml
23
+ end
24
+
25
+ it "should raise error on not exists option" do
26
+ lambda {
27
+ Vkontakte.config.some_param
28
+ }.should raise_error(StandardError)
29
+ end
30
+ end
31
+ end
metadata ADDED
@@ -0,0 +1,125 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: vkontakte
3
+ version: !ruby/object:Gem::Version
4
+ hash: 29
5
+ prerelease:
6
+ segments:
7
+ - 0
8
+ - 0
9
+ - 1
10
+ version: 0.0.1
11
+ platform: ruby
12
+ authors:
13
+ - Igor Galeta
14
+ - Pavlo Galeta
15
+ autorequire:
16
+ bindir: bin
17
+ cert_chain: []
18
+
19
+ date: 2011-11-11 00:00:00 +02:00
20
+ default_executable:
21
+ dependencies:
22
+ - !ruby/object:Gem::Dependency
23
+ name: activesupport
24
+ prerelease: false
25
+ requirement: &id001 !ruby/object:Gem::Requirement
26
+ none: false
27
+ requirements:
28
+ - - ">="
29
+ - !ruby/object:Gem::Version
30
+ hash: 3
31
+ segments:
32
+ - 0
33
+ version: "0"
34
+ type: :runtime
35
+ version_requirements: *id001
36
+ - !ruby/object:Gem::Dependency
37
+ name: httparty
38
+ prerelease: false
39
+ requirement: &id002 !ruby/object:Gem::Requirement
40
+ none: false
41
+ requirements:
42
+ - - ">="
43
+ - !ruby/object:Gem::Version
44
+ hash: 3
45
+ segments:
46
+ - 0
47
+ version: "0"
48
+ type: :runtime
49
+ version_requirements: *id002
50
+ description: "The easiest way to access Vkontakte API and some other utils. "
51
+ email: galeta.igor@gmail.com
52
+ executables: []
53
+
54
+ extensions: []
55
+
56
+ extra_rdoc_files:
57
+ - README.rdoc
58
+ files:
59
+ - lib/vkontakte/app/secure.rb
60
+ - lib/vkontakte/app/iframe.rb
61
+ - lib/vkontakte/app/base.rb
62
+ - lib/vkontakte/utils.rb
63
+ - lib/vkontakte/api/secure.rb
64
+ - lib/vkontakte/api/friends.rb
65
+ - lib/vkontakte/api/groups.rb
66
+ - lib/vkontakte/api/base.rb
67
+ - lib/vkontakte/api/photos.rb
68
+ - lib/vkontakte/config.rb
69
+ - lib/vkontakte/version.rb
70
+ - lib/vkontakte.rb
71
+ - MIT-LICENSE
72
+ - Rakefile
73
+ - Gemfile
74
+ - README.rdoc
75
+ - spec/api/secure_spec.rb
76
+ - spec/api/friends_spec.rb
77
+ - spec/api/photos_spec.rb
78
+ - spec/api/groups_spec.rb
79
+ - spec/vkontakte_spec.rb
80
+ - spec/apps/base_spec.rb
81
+ - spec/apps/iframe_spec.rb
82
+ - spec/spec_helper.rb
83
+ has_rdoc: true
84
+ homepage: https://github.com/galetahub/vkontakte
85
+ licenses: []
86
+
87
+ post_install_message:
88
+ rdoc_options: []
89
+
90
+ require_paths:
91
+ - lib
92
+ required_ruby_version: !ruby/object:Gem::Requirement
93
+ none: false
94
+ requirements:
95
+ - - ">="
96
+ - !ruby/object:Gem::Version
97
+ hash: 3
98
+ segments:
99
+ - 0
100
+ version: "0"
101
+ required_rubygems_version: !ruby/object:Gem::Requirement
102
+ none: false
103
+ requirements:
104
+ - - ">="
105
+ - !ruby/object:Gem::Version
106
+ hash: 3
107
+ segments:
108
+ - 0
109
+ version: "0"
110
+ requirements: []
111
+
112
+ rubyforge_project:
113
+ rubygems_version: 1.6.2
114
+ signing_key:
115
+ specification_version: 3
116
+ summary: Vkontakte API
117
+ test_files:
118
+ - spec/api/secure_spec.rb
119
+ - spec/api/friends_spec.rb
120
+ - spec/api/photos_spec.rb
121
+ - spec/api/groups_spec.rb
122
+ - spec/vkontakte_spec.rb
123
+ - spec/apps/base_spec.rb
124
+ - spec/apps/iframe_spec.rb
125
+ - spec/spec_helper.rb