vkontakte 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
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