googl 0.6.3 → 0.7.0
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.
- checksums.yaml +7 -0
- data/.travis.yml +3 -2
- data/Gemfile +4 -5
- data/README.markdown +252 -0
- data/Rakefile +0 -10
- data/VERSION +1 -1
- data/googl.gemspec +20 -23
- data/lib/googl.rb +2 -2
- data/lib/googl/shorten.rb +13 -3
- data/spec/googl/client_spec.rb +1 -1
- data/spec/googl/oauth2/native_spec.rb +12 -9
- data/spec/googl/oauth2/server_spec.rb +14 -9
- data/spec/googl/shorten_spec.rb +30 -0
- data/spec/spec_helper.rb +25 -0
- metadata +32 -66
- data/Gemfile.lock +0 -45
- data/README.rdoc +0 -188
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: dd775bcf591d39709171b85ac2d63bf97fc2d2b1
|
4
|
+
data.tar.gz: 6ec26939a7009c3e8be6361f4803c5c035d61cc6
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: d99253182986d9a24c544ecb29c1e2c90d893f735048f9a3b47201817eb795d12890b03f6ec5a8dcd8a585589d40ab40630856602afc4d3e5978c7a3234ed573
|
7
|
+
data.tar.gz: b822ffe4e5ae1a14cd96f156e65d7298036fd95d5f5e2a1e68f1c90c4a4cdf198dd9698ff028f15b4f5feac010858fc6966e99379cdee26c1db418dab8432595
|
data/.travis.yml
CHANGED
data/Gemfile
CHANGED
@@ -1,12 +1,11 @@
|
|
1
1
|
source "http://rubygems.org"
|
2
2
|
|
3
|
-
gem "httparty", "~> 0.10
|
3
|
+
gem "httparty", "~> 0.10"
|
4
4
|
gem "json", ">= 1.4.6"
|
5
5
|
|
6
6
|
group :development do
|
7
|
-
gem "rspec", "~> 2.10
|
8
|
-
gem "
|
9
|
-
gem "jeweler", "~> 1.8.3"
|
10
|
-
gem "rcov", ">= 0"
|
7
|
+
gem "rspec", "~> 2.10"
|
8
|
+
gem "jeweler", ">= 1.8.3"
|
11
9
|
gem "webmock", "~> 1.8.6"
|
10
|
+
gem "timecop", "~> 0.7.1"
|
12
11
|
end
|
data/README.markdown
ADDED
@@ -0,0 +1,252 @@
|
|
1
|
+
# googl
|
2
|
+
|
3
|
+
Google URL Shortener API in Ruby
|
4
|
+
|
5
|
+
## Continuous Integration
|
6
|
+
|
7
|
+
[](https://travis-ci.org/zigotto/googl)
|
8
|
+
[](http://badge.fury.io/rb/googl)
|
9
|
+
|
10
|
+
## Basic Usage
|
11
|
+
|
12
|
+
### Shorten a long URL
|
13
|
+
|
14
|
+
``` ruby
|
15
|
+
url = Googl.shorten('http://www.zigotto.com')
|
16
|
+
|
17
|
+
// Optional - provide a user_ip and your google api_key to bypass Google's request rate limits
|
18
|
+
url = Googl.shorten('http://www.zigotto.com', "213.57.23.49", "google_api_key")
|
19
|
+
|
20
|
+
url.short_url
|
21
|
+
#=> "http://goo.gl/ump4S"
|
22
|
+
|
23
|
+
url.long_url
|
24
|
+
#=> "http://www.zigotto.com/"
|
25
|
+
|
26
|
+
url.qr_code
|
27
|
+
#=> "http://goo.gl/ump4S.qr"
|
28
|
+
|
29
|
+
url.info
|
30
|
+
#=> "http://goo.gl/ump4S.info"
|
31
|
+
```
|
32
|
+
|
33
|
+
### Expand a short URL
|
34
|
+
|
35
|
+
``` ruby
|
36
|
+
url = Googl.expand('http://goo.gl/ump4S')
|
37
|
+
|
38
|
+
url.long_url
|
39
|
+
#=> "http://www.zigotto.com/"
|
40
|
+
```
|
41
|
+
|
42
|
+
## Authentication
|
43
|
+
|
44
|
+
The Google URL Shortener API supports the OAuth and ClientLogin mechanisms for authenticating.
|
45
|
+
|
46
|
+
For shorten requests, each short URL for an authenticated user is unique, and thus will gather unique click statistics. In addition, it shows up on the user's dashboard at http://goo.gl.
|
47
|
+
|
48
|
+
### ClientLogin
|
49
|
+
|
50
|
+
``` ruby
|
51
|
+
client = Googl.client('user@gmail.com', 'my_valid_password')
|
52
|
+
|
53
|
+
url = client.shorten('https://github.com/zigotto/googl')
|
54
|
+
url.short_url
|
55
|
+
#=> http://goo.gl/DWDfi
|
56
|
+
```
|
57
|
+
|
58
|
+
Go to http://goo.gl to see URL statistics.
|
59
|
+
|
60
|
+
### OAuth
|
61
|
+
|
62
|
+
Google supports three flows of OAuth 2.0
|
63
|
+
|
64
|
+
* The <b>client-side</b> flow for JavaScript applications running in a browser. (TODO)
|
65
|
+
* The <b>server-side</b> flow for web applications with servers that can securely store persistent information.
|
66
|
+
* The <b>native application</b> flow for desktop and mobile applications.
|
67
|
+
|
68
|
+
Today, gem googl support only <b>server-side</b> and <b>native application</b>.
|
69
|
+
|
70
|
+
#### Server-side web applications
|
71
|
+
|
72
|
+
You'll need to register your application with Google to get a `client_id` and record the `redirect_uri` you'd like to use.
|
73
|
+
See the [Registering](http://code.google.com/apis/accounts/docs/OAuth2.html#Registering) your app with Google section for details on how to register.
|
74
|
+
|
75
|
+
``` ruby
|
76
|
+
client = Googl::OAuth2.server("client_id", "client_secret", "redirect_uri")
|
77
|
+
```
|
78
|
+
|
79
|
+
Redirect your users to
|
80
|
+
|
81
|
+
``` ruby
|
82
|
+
client.authorize_url
|
83
|
+
```
|
84
|
+
|
85
|
+
After the user approves access or chooses not to, we'll redirect to the <em>redirect_uri</em> you passed us and append either an authorization code.
|
86
|
+
|
87
|
+
``` ruby
|
88
|
+
client.request_access_token(params["code"])
|
89
|
+
client.authorized?
|
90
|
+
#=> true
|
91
|
+
```
|
92
|
+
|
93
|
+
Now you can get a user's history, shorten url, etc...
|
94
|
+
|
95
|
+
``` ruby
|
96
|
+
history = client.history
|
97
|
+
history.total_items
|
98
|
+
#=> 19
|
99
|
+
```
|
100
|
+
|
101
|
+
``` ruby
|
102
|
+
url = Googl.shorten('https://github.com/zigotto/googl')
|
103
|
+
url.short_url
|
104
|
+
#=> http://goo.gl/DWDfi
|
105
|
+
```
|
106
|
+
|
107
|
+
#### Native applications
|
108
|
+
|
109
|
+
You'll need to register your application with Google to get a `client_id` and record the special `redirect_uri: urn:ietf:wg:oauth:2.0:oob`.
|
110
|
+
See the [Registering](http://code.google.com/apis/accounts/docs/OAuth2.html#Registering) your app with Google section for details on how to register.
|
111
|
+
|
112
|
+
When you use this redirect_uri, instead of redirecting the user's browser to a page on your site with an authorization code, Google will display the authorization code or error response in the title of the page and a text field with instructions for the user to copy and paste it in to your application. Your application can either monitor the window title of a web-view or browser window it launches and close it once the authorization code appears or prompt the user to copy and paste the code.
|
113
|
+
|
114
|
+
``` ruby
|
115
|
+
client = Googl::OAuth2.native("client_id", "client_secret")
|
116
|
+
```
|
117
|
+
|
118
|
+
Open a browser or a webview to the OAuth dialog at
|
119
|
+
|
120
|
+
``` ruby
|
121
|
+
client.authorize_url
|
122
|
+
```
|
123
|
+
|
124
|
+
In the native app flow, after the user approves access, we'll display the authorization code in the title of the page and in a text input with instructions for the user to copy and paste the code to your application
|
125
|
+
|
126
|
+
``` ruby
|
127
|
+
code = "copied code"
|
128
|
+
client.request_access_token(code)
|
129
|
+
client.authorized?
|
130
|
+
#=> true
|
131
|
+
```
|
132
|
+
|
133
|
+
## Analytics
|
134
|
+
|
135
|
+
Expands a short URL or gets creation time and analytics
|
136
|
+
|
137
|
+
For analytics and additional information to return (using the `:projection` parameter)
|
138
|
+
|
139
|
+
* *:full* => returns the creation timestamp and all available analytics
|
140
|
+
* *:analytics_clicks* => returns only click counts
|
141
|
+
* *:analytics_top_strings* => returns only top string counts (e.g. referrers, countries, etc)
|
142
|
+
|
143
|
+
### Get Analytics
|
144
|
+
|
145
|
+
``` ruby
|
146
|
+
url = Googl.expand('http://goo.gl/DWDfi', :projection => :full)
|
147
|
+
|
148
|
+
url.analytics.all_time.browsers.first.label
|
149
|
+
#=> "Chrome"
|
150
|
+
|
151
|
+
url.analytics.all_time.browsers.first.count
|
152
|
+
#=> "11"
|
153
|
+
```
|
154
|
+
|
155
|
+
A summary of the click analytics for the short and long URL
|
156
|
+
|
157
|
+
``` ruby
|
158
|
+
url.analytics
|
159
|
+
```
|
160
|
+
|
161
|
+
Analytics details for a particular window of time; counts in this object are of clicks that occurred within the most recent window of this length.
|
162
|
+
|
163
|
+
``` ruby
|
164
|
+
url.analytics.all_time
|
165
|
+
url.analytics.month
|
166
|
+
url.analytics.week
|
167
|
+
url.analytics.day
|
168
|
+
url.analytics.two_hours
|
169
|
+
```
|
170
|
+
|
171
|
+
Number of clicks on this short URL. Replace (*) for all_time, month, week, day or two_hours
|
172
|
+
|
173
|
+
``` ruby
|
174
|
+
url.analytics.*.short_url_clicks
|
175
|
+
```
|
176
|
+
|
177
|
+
Number of clicks on all goo.gl short URLs pointing to this long URL.
|
178
|
+
|
179
|
+
``` ruby
|
180
|
+
url.analytics.*.long_url_clicks
|
181
|
+
```
|
182
|
+
|
183
|
+
Top referring hosts, e.g. "www.google.com"; sorted by (descending) click counts. Only present if this data is available.
|
184
|
+
|
185
|
+
``` ruby
|
186
|
+
url.analytics.*.referrers
|
187
|
+
```
|
188
|
+
|
189
|
+
Top countries (expressed as country codes), e.g. "US" or "DE"; sorted by (descending) click counts.
|
190
|
+
|
191
|
+
``` ruby
|
192
|
+
url.analytics.*.countries
|
193
|
+
```
|
194
|
+
|
195
|
+
Top browsers, e.g. "Chrome"; sorted by (descending) click counts.
|
196
|
+
|
197
|
+
``` ruby
|
198
|
+
url.analytics.*.browsers
|
199
|
+
```
|
200
|
+
|
201
|
+
Top platforms or OSes, e.g. "Windows"; sorted by (descending) click counts.
|
202
|
+
|
203
|
+
``` ruby
|
204
|
+
url.analytics.*.platforms
|
205
|
+
```
|
206
|
+
|
207
|
+
For details, see http://code.google.com/intl/pt-BR/apis/urlshortener/v1/reference.html#resource_url
|
208
|
+
|
209
|
+
## History
|
210
|
+
|
211
|
+
Gets a user's history of shortened URLs. (Authenticated)
|
212
|
+
|
213
|
+
``` ruby
|
214
|
+
client = Googl.client('user@gmail.com', 'my_valid_password')
|
215
|
+
|
216
|
+
history = client.history
|
217
|
+
history.total_items
|
218
|
+
#=> 19
|
219
|
+
```
|
220
|
+
|
221
|
+
A list of URL.
|
222
|
+
|
223
|
+
``` ruby
|
224
|
+
history.items
|
225
|
+
```
|
226
|
+
|
227
|
+
Analytics details for all items
|
228
|
+
|
229
|
+
``` ruby
|
230
|
+
history = client.history(:projection => :analytics_clicks)
|
231
|
+
```
|
232
|
+
|
233
|
+
## Installation
|
234
|
+
|
235
|
+
```console
|
236
|
+
gem install googl
|
237
|
+
```
|
238
|
+
|
239
|
+
## Note on Patches/Pull Requests
|
240
|
+
|
241
|
+
* Fork the project.
|
242
|
+
* Make your feature addition or bug fix.
|
243
|
+
* Add tests for it. This is important so I don't break it in a future
|
244
|
+
version unintentionally.
|
245
|
+
* Commit, do not mess with rakefile, version, or history. (if you want to
|
246
|
+
have your own version, that is fine but bump version in a commit by itself
|
247
|
+
I can ignore when I pull)
|
248
|
+
* Send me a pull request. Bonus points for topic branches.
|
249
|
+
|
250
|
+
## License
|
251
|
+
|
252
|
+
MIT License. Copyright 2011-2015 Zigotto®. http://www.zigotto.com.br
|
data/Rakefile
CHANGED
@@ -36,13 +36,3 @@ RSpec::Core::RakeTask.new(:rcov) do |spec|
|
|
36
36
|
end
|
37
37
|
|
38
38
|
task :default => :spec
|
39
|
-
|
40
|
-
require 'rake/rdoctask'
|
41
|
-
Rake::RDocTask.new do |rdoc|
|
42
|
-
version = File.exist?('VERSION') ? File.read('VERSION') : ""
|
43
|
-
|
44
|
-
rdoc.rdoc_dir = 'rdoc'
|
45
|
-
rdoc.title = "googl #{version}"
|
46
|
-
rdoc.rdoc_files.include('README*')
|
47
|
-
rdoc.rdoc_files.include('lib/**/*.rb')
|
48
|
-
end
|
data/VERSION
CHANGED
@@ -1 +1 @@
|
|
1
|
-
0.
|
1
|
+
0.7.0
|
data/googl.gemspec
CHANGED
@@ -2,25 +2,26 @@
|
|
2
2
|
# DO NOT EDIT THIS FILE DIRECTLY
|
3
3
|
# Instead, edit Jeweler::Tasks in Rakefile, and run 'rake gemspec'
|
4
4
|
# -*- encoding: utf-8 -*-
|
5
|
+
# stub: googl 0.7.0 ruby lib
|
5
6
|
|
6
7
|
Gem::Specification.new do |s|
|
7
8
|
s.name = "googl"
|
8
|
-
s.version = "0.
|
9
|
+
s.version = "0.7.0"
|
9
10
|
|
10
11
|
s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
|
12
|
+
s.require_paths = ["lib"]
|
11
13
|
s.authors = ["Jesus Lopes"]
|
12
|
-
s.date = "
|
14
|
+
s.date = "2015-02-06"
|
13
15
|
s.description = "Small library for Google URL Shortener API"
|
14
16
|
s.email = "jlopes@zigotto.com.br"
|
15
17
|
s.extra_rdoc_files = [
|
16
|
-
"README.
|
18
|
+
"README.markdown"
|
17
19
|
]
|
18
20
|
s.files = [
|
19
21
|
".travis.yml",
|
20
22
|
"Gemfile",
|
21
|
-
"Gemfile.lock",
|
22
23
|
"MIT-LICENSE",
|
23
|
-
"README.
|
24
|
+
"README.markdown",
|
24
25
|
"Rakefile",
|
25
26
|
"VERSION",
|
26
27
|
"googl.gemspec",
|
@@ -65,38 +66,34 @@ Gem::Specification.new do |s|
|
|
65
66
|
]
|
66
67
|
s.homepage = "http://github.com/zigotto/googl"
|
67
68
|
s.licenses = ["MIT"]
|
68
|
-
s.
|
69
|
-
s.rubygems_version = "1.8.23"
|
69
|
+
s.rubygems_version = "2.4.5"
|
70
70
|
s.summary = "Wrapper for Google URL Shortener API"
|
71
71
|
|
72
72
|
if s.respond_to? :specification_version then
|
73
|
-
s.specification_version =
|
73
|
+
s.specification_version = 4
|
74
74
|
|
75
75
|
if Gem::Version.new(Gem::VERSION) >= Gem::Version.new('1.2.0') then
|
76
|
-
s.add_runtime_dependency(%q<httparty>, ["~> 0.10
|
76
|
+
s.add_runtime_dependency(%q<httparty>, ["~> 0.10"])
|
77
77
|
s.add_runtime_dependency(%q<json>, [">= 1.4.6"])
|
78
|
-
s.add_development_dependency(%q<rspec>, ["~> 2.10
|
79
|
-
s.add_development_dependency(%q<
|
80
|
-
s.add_development_dependency(%q<jeweler>, ["~> 1.8.3"])
|
81
|
-
s.add_development_dependency(%q<rcov>, [">= 0"])
|
78
|
+
s.add_development_dependency(%q<rspec>, ["~> 2.10"])
|
79
|
+
s.add_development_dependency(%q<jeweler>, [">= 1.8.3"])
|
82
80
|
s.add_development_dependency(%q<webmock>, ["~> 1.8.6"])
|
81
|
+
s.add_development_dependency(%q<timecop>, ["~> 0.7.1"])
|
83
82
|
else
|
84
|
-
s.add_dependency(%q<httparty>, ["~> 0.10
|
83
|
+
s.add_dependency(%q<httparty>, ["~> 0.10"])
|
85
84
|
s.add_dependency(%q<json>, [">= 1.4.6"])
|
86
|
-
s.add_dependency(%q<rspec>, ["~> 2.10
|
87
|
-
s.add_dependency(%q<
|
88
|
-
s.add_dependency(%q<jeweler>, ["~> 1.8.3"])
|
89
|
-
s.add_dependency(%q<rcov>, [">= 0"])
|
85
|
+
s.add_dependency(%q<rspec>, ["~> 2.10"])
|
86
|
+
s.add_dependency(%q<jeweler>, [">= 1.8.3"])
|
90
87
|
s.add_dependency(%q<webmock>, ["~> 1.8.6"])
|
88
|
+
s.add_dependency(%q<timecop>, ["~> 0.7.1"])
|
91
89
|
end
|
92
90
|
else
|
93
|
-
s.add_dependency(%q<httparty>, ["~> 0.10
|
91
|
+
s.add_dependency(%q<httparty>, ["~> 0.10"])
|
94
92
|
s.add_dependency(%q<json>, [">= 1.4.6"])
|
95
|
-
s.add_dependency(%q<rspec>, ["~> 2.10
|
96
|
-
s.add_dependency(%q<
|
97
|
-
s.add_dependency(%q<jeweler>, ["~> 1.8.3"])
|
98
|
-
s.add_dependency(%q<rcov>, [">= 0"])
|
93
|
+
s.add_dependency(%q<rspec>, ["~> 2.10"])
|
94
|
+
s.add_dependency(%q<jeweler>, [">= 1.8.3"])
|
99
95
|
s.add_dependency(%q<webmock>, ["~> 1.8.6"])
|
96
|
+
s.add_dependency(%q<timecop>, ["~> 0.7.1"])
|
100
97
|
end
|
101
98
|
end
|
102
99
|
|
data/lib/googl.rb
CHANGED
@@ -23,9 +23,9 @@ module Googl
|
|
23
23
|
# url.short_url
|
24
24
|
# => "http://goo.gl/ump4S"
|
25
25
|
#
|
26
|
-
def shorten(url=nil)
|
26
|
+
def shorten(url=nil, user_ip = nil, api_key = nil)
|
27
27
|
raise ArgumentError.new("URL to shorten is required") if url.nil? || url.strip.empty?
|
28
|
-
Googl::Shorten.new(url)
|
28
|
+
Googl::Shorten.new(url, user_ip, api_key)
|
29
29
|
end
|
30
30
|
|
31
31
|
# Expands a short URL or gets creation time and analytics
|
data/lib/googl/shorten.rb
CHANGED
@@ -8,10 +8,20 @@ module Googl
|
|
8
8
|
|
9
9
|
# Creates a new short URL, see Googl.shorten
|
10
10
|
#
|
11
|
-
def initialize(long_url)
|
11
|
+
def initialize(long_url, user_ip = nil, api_key = nil)
|
12
12
|
modify_headers('Content-Type' => 'application/json')
|
13
|
-
options = {"longUrl" => long_url}
|
14
|
-
|
13
|
+
options = {"longUrl" => long_url}
|
14
|
+
shorten_url = API_URL
|
15
|
+
|
16
|
+
if (user_ip != nil && !user_ip.empty?)
|
17
|
+
options["userIp"] = user_ip
|
18
|
+
end
|
19
|
+
if (api_key != nil && !api_key.empty?)
|
20
|
+
shorten_url += "?key=#{api_key}"
|
21
|
+
end
|
22
|
+
|
23
|
+
options_json = options.to_json
|
24
|
+
resp = post(shorten_url, :body => options_json)
|
15
25
|
if resp.code == 200
|
16
26
|
self.short_url = resp['id']
|
17
27
|
self.long_url = resp['longUrl']
|
data/spec/googl/client_spec.rb
CHANGED
@@ -85,7 +85,7 @@ describe Googl::OAuth2::Native do
|
|
85
85
|
|
86
86
|
before do
|
87
87
|
@now = Time.now
|
88
|
-
Time.stub
|
88
|
+
Time.stub(:now).and_return(@now)
|
89
89
|
end
|
90
90
|
|
91
91
|
let(:native) { subject.request_access_token("4/SuSud6RqPojUXsPpeh-wSVCwnmTQ") }
|
@@ -97,33 +97,36 @@ describe Googl::OAuth2::Native do
|
|
97
97
|
end
|
98
98
|
|
99
99
|
describe "#expires?" do
|
100
|
-
|
101
|
-
|
102
|
-
Time.stub!(:now).and_return(Time.parse("2011-04-23 15:30:00"))
|
100
|
+
before do
|
101
|
+
Timecop.freeze(DateTime.parse("2011-04-23 15:30:00"))
|
103
102
|
subject.request_access_token("4/SuSud6RqPojUXsPpeh-wSVCwnmTQ")
|
104
103
|
end
|
105
104
|
|
106
105
|
it "should be true if access token expires" do
|
107
|
-
|
108
|
-
|
106
|
+
Timecop.freeze(DateTime.parse("2011-04-23 18:30:00")) do
|
107
|
+
subject.expires?.should be true
|
108
|
+
end
|
109
109
|
end
|
110
110
|
|
111
111
|
it "should be false if access token not expires" do
|
112
|
-
subject.expires?.should
|
112
|
+
subject.expires?.should be false
|
113
113
|
end
|
114
114
|
|
115
|
+
after do
|
116
|
+
Timecop.return
|
117
|
+
end
|
115
118
|
end
|
116
119
|
|
117
120
|
describe "#authorized?" do
|
118
121
|
|
119
122
|
it "should return false if client is not authorized" do
|
120
|
-
subject.authorized?.should
|
123
|
+
subject.authorized?.should be false
|
121
124
|
end
|
122
125
|
|
123
126
|
let(:native) { subject.request_access_token("4/SuSud6RqPojUXsPpeh-wSVCwnmTQ") }
|
124
127
|
|
125
128
|
it "should return true if client is authorized" do
|
126
|
-
native.authorized?.should
|
129
|
+
native.authorized?.should be true
|
127
130
|
end
|
128
131
|
|
129
132
|
end
|
@@ -86,10 +86,9 @@ describe Googl::OAuth2::Server do
|
|
86
86
|
end
|
87
87
|
|
88
88
|
describe "#expires_at" do
|
89
|
-
|
90
89
|
before do
|
91
90
|
@now = Time.now
|
92
|
-
|
91
|
+
Timecop.freeze(@now)
|
93
92
|
end
|
94
93
|
|
95
94
|
let(:server) { subject.request_access_token("4/z43CZpNmqd0IO3dR1Y_ouase13CH") }
|
@@ -98,36 +97,42 @@ describe Googl::OAuth2::Server do
|
|
98
97
|
server.expires_at.should == (@now + 3600)
|
99
98
|
end
|
100
99
|
|
100
|
+
after do
|
101
|
+
Timecop.return
|
102
|
+
end
|
101
103
|
end
|
102
104
|
|
103
105
|
describe "#expires?" do
|
104
|
-
|
105
106
|
before :each do
|
106
|
-
|
107
|
+
Timecop.freeze(DateTime.parse("2011-04-23 15:30:00"))
|
107
108
|
subject.request_access_token("4/z43CZpNmqd0IO3dR1Y_ouase13CH")
|
108
109
|
end
|
109
110
|
|
110
111
|
it "should be true if access token expires" do
|
111
|
-
|
112
|
-
|
112
|
+
Timecop.freeze(DateTime.parse("2011-04-23 18:30:00")) do
|
113
|
+
subject.expires?.should be true
|
114
|
+
end
|
113
115
|
end
|
114
116
|
|
115
117
|
it "should be false if access token not expires" do
|
116
|
-
subject.expires?.should
|
118
|
+
subject.expires?.should be false
|
117
119
|
end
|
118
120
|
|
121
|
+
after do
|
122
|
+
Timecop.return
|
123
|
+
end
|
119
124
|
end
|
120
125
|
|
121
126
|
describe "#authorized?" do
|
122
127
|
|
123
128
|
it "should return false if client is not authorized" do
|
124
|
-
subject.authorized?.should
|
129
|
+
subject.authorized?.should be false
|
125
130
|
end
|
126
131
|
|
127
132
|
let(:server) { subject.request_access_token("4/z43CZpNmqd0IO3dR1Y_ouase13CH") }
|
128
133
|
|
129
134
|
it "should return true if client is authorized" do
|
130
|
-
server.authorized?.should
|
135
|
+
server.authorized?.should be true
|
131
136
|
end
|
132
137
|
|
133
138
|
end
|
data/spec/googl/shorten_spec.rb
CHANGED
@@ -53,6 +53,36 @@ describe Googl::Shorten do
|
|
53
53
|
|
54
54
|
end
|
55
55
|
|
56
|
+
context "with user_ip" do
|
57
|
+
subject { Googl.shorten('http://www.zigotto.com', "54.154.97.74") }
|
58
|
+
|
59
|
+
describe "#short_url" do
|
60
|
+
it "should return a short URL" do
|
61
|
+
subject.short_url.should == 'http://goo.gl/ump4S'
|
62
|
+
end
|
63
|
+
end
|
64
|
+
end
|
65
|
+
|
66
|
+
context "with api_key" do
|
67
|
+
subject { Googl.shorten('http://www.zigotto.com', nil, "Abc123") }
|
68
|
+
|
69
|
+
describe "#short_url" do
|
70
|
+
it "should return a short URL" do
|
71
|
+
subject.short_url.should == 'http://goo.gl/ump4S'
|
72
|
+
end
|
73
|
+
end
|
74
|
+
end
|
75
|
+
|
76
|
+
context "with user_ip and api_key" do
|
77
|
+
subject { Googl.shorten('http://www.zigotto.com', nil, "Abc123") }
|
78
|
+
|
79
|
+
describe "#short_url" do
|
80
|
+
it "should return a short URL" do
|
81
|
+
subject.short_url.should == 'http://goo.gl/ump4S'
|
82
|
+
end
|
83
|
+
end
|
84
|
+
end
|
85
|
+
|
56
86
|
end
|
57
87
|
|
58
88
|
end
|
data/spec/spec_helper.rb
CHANGED
@@ -4,6 +4,7 @@ $LOAD_PATH.unshift(File.join(File.dirname(__FILE__), '..', 'lib'))
|
|
4
4
|
require "rubygems"
|
5
5
|
require "rspec"
|
6
6
|
require 'webmock/rspec'
|
7
|
+
require 'timecop'
|
7
8
|
require 'googl'
|
8
9
|
|
9
10
|
require 'shared_examples'
|
@@ -30,6 +31,30 @@ def fake_urls?(status)
|
|
30
31
|
with(:body => params).
|
31
32
|
to_return(load_fixture('shorten_invalid_content_type.json'))
|
32
33
|
|
34
|
+
# Shorten with user_ip
|
35
|
+
url_shorten = "https://www.googleapis.com/urlshortener/v1/url"
|
36
|
+
params = "{\"longUrl\":\"http://www.zigotto.com\",\"userIp\":\"54.154.97.74\"}" #json
|
37
|
+
stub_request(:post, url_shorten).
|
38
|
+
with(:body => params,
|
39
|
+
:headers => {'Content-Type'=>'application/json'}).
|
40
|
+
to_return(load_fixture('shorten.json'))
|
41
|
+
|
42
|
+
# Shorten with api_key
|
43
|
+
url_shorten_with_key = "https://www.googleapis.com/urlshortener/v1/url?key=Abc123"
|
44
|
+
params = "{\"longUrl\":\"http://www.zigotto.com\"}" #json
|
45
|
+
stub_request(:post, url_shorten_with_key).
|
46
|
+
with(:body => params,
|
47
|
+
:headers => {'Content-Type'=>'application/json'}).
|
48
|
+
to_return(load_fixture('shorten.json'))
|
49
|
+
|
50
|
+
# Shorten with user_ip and api_key
|
51
|
+
url_shorten_with_key = "https://www.googleapis.com/urlshortener/v1/url?key=Abc123"
|
52
|
+
params = "{\"longUrl\":\"http://www.zigotto.com\",\"userIp\":\"54.154.97.74\"}" #json
|
53
|
+
stub_request(:post, url_shorten_with_key).
|
54
|
+
with(:body => params,
|
55
|
+
:headers => {'Content-Type'=>'application/json'}).
|
56
|
+
to_return(load_fixture('shorten.json'))
|
57
|
+
|
33
58
|
# Expand
|
34
59
|
url_expand = "https://www.googleapis.com/urlshortener/v1/url?shortUrl=http://goo.gl/7lob"
|
35
60
|
stub_request(:get, url_expand).to_return(load_fixture('expand.json'))
|
metadata
CHANGED
@@ -1,140 +1,110 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: googl
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
5
|
-
prerelease:
|
4
|
+
version: 0.7.0
|
6
5
|
platform: ruby
|
7
6
|
authors:
|
8
7
|
- Jesus Lopes
|
9
8
|
autorequire:
|
10
9
|
bindir: bin
|
11
10
|
cert_chain: []
|
12
|
-
date:
|
11
|
+
date: 2015-02-06 00:00:00.000000000 Z
|
13
12
|
dependencies:
|
14
13
|
- !ruby/object:Gem::Dependency
|
15
14
|
name: httparty
|
16
15
|
requirement: !ruby/object:Gem::Requirement
|
17
|
-
none: false
|
18
16
|
requirements:
|
19
|
-
- - ~>
|
17
|
+
- - "~>"
|
20
18
|
- !ruby/object:Gem::Version
|
21
|
-
version: 0.10
|
19
|
+
version: '0.10'
|
22
20
|
type: :runtime
|
23
21
|
prerelease: false
|
24
22
|
version_requirements: !ruby/object:Gem::Requirement
|
25
|
-
none: false
|
26
23
|
requirements:
|
27
|
-
- - ~>
|
24
|
+
- - "~>"
|
28
25
|
- !ruby/object:Gem::Version
|
29
|
-
version: 0.10
|
26
|
+
version: '0.10'
|
30
27
|
- !ruby/object:Gem::Dependency
|
31
28
|
name: json
|
32
29
|
requirement: !ruby/object:Gem::Requirement
|
33
|
-
none: false
|
34
30
|
requirements:
|
35
|
-
- -
|
31
|
+
- - ">="
|
36
32
|
- !ruby/object:Gem::Version
|
37
33
|
version: 1.4.6
|
38
34
|
type: :runtime
|
39
35
|
prerelease: false
|
40
36
|
version_requirements: !ruby/object:Gem::Requirement
|
41
|
-
none: false
|
42
37
|
requirements:
|
43
|
-
- -
|
38
|
+
- - ">="
|
44
39
|
- !ruby/object:Gem::Version
|
45
40
|
version: 1.4.6
|
46
41
|
- !ruby/object:Gem::Dependency
|
47
42
|
name: rspec
|
48
43
|
requirement: !ruby/object:Gem::Requirement
|
49
|
-
none: false
|
50
44
|
requirements:
|
51
|
-
- - ~>
|
45
|
+
- - "~>"
|
52
46
|
- !ruby/object:Gem::Version
|
53
|
-
version: 2.10
|
47
|
+
version: '2.10'
|
54
48
|
type: :development
|
55
49
|
prerelease: false
|
56
50
|
version_requirements: !ruby/object:Gem::Requirement
|
57
|
-
none: false
|
58
51
|
requirements:
|
59
|
-
- - ~>
|
52
|
+
- - "~>"
|
60
53
|
- !ruby/object:Gem::Version
|
61
|
-
version: 2.10
|
62
|
-
- !ruby/object:Gem::Dependency
|
63
|
-
name: bundler
|
64
|
-
requirement: !ruby/object:Gem::Requirement
|
65
|
-
none: false
|
66
|
-
requirements:
|
67
|
-
- - ~>
|
68
|
-
- !ruby/object:Gem::Version
|
69
|
-
version: 1.2.3
|
70
|
-
type: :development
|
71
|
-
prerelease: false
|
72
|
-
version_requirements: !ruby/object:Gem::Requirement
|
73
|
-
none: false
|
74
|
-
requirements:
|
75
|
-
- - ~>
|
76
|
-
- !ruby/object:Gem::Version
|
77
|
-
version: 1.2.3
|
54
|
+
version: '2.10'
|
78
55
|
- !ruby/object:Gem::Dependency
|
79
56
|
name: jeweler
|
80
57
|
requirement: !ruby/object:Gem::Requirement
|
81
|
-
none: false
|
82
58
|
requirements:
|
83
|
-
- -
|
59
|
+
- - ">="
|
84
60
|
- !ruby/object:Gem::Version
|
85
61
|
version: 1.8.3
|
86
62
|
type: :development
|
87
63
|
prerelease: false
|
88
64
|
version_requirements: !ruby/object:Gem::Requirement
|
89
|
-
none: false
|
90
65
|
requirements:
|
91
|
-
- -
|
66
|
+
- - ">="
|
92
67
|
- !ruby/object:Gem::Version
|
93
68
|
version: 1.8.3
|
94
69
|
- !ruby/object:Gem::Dependency
|
95
|
-
name:
|
70
|
+
name: webmock
|
96
71
|
requirement: !ruby/object:Gem::Requirement
|
97
|
-
none: false
|
98
72
|
requirements:
|
99
|
-
- -
|
73
|
+
- - "~>"
|
100
74
|
- !ruby/object:Gem::Version
|
101
|
-
version:
|
75
|
+
version: 1.8.6
|
102
76
|
type: :development
|
103
77
|
prerelease: false
|
104
78
|
version_requirements: !ruby/object:Gem::Requirement
|
105
|
-
none: false
|
106
79
|
requirements:
|
107
|
-
- -
|
80
|
+
- - "~>"
|
108
81
|
- !ruby/object:Gem::Version
|
109
|
-
version:
|
82
|
+
version: 1.8.6
|
110
83
|
- !ruby/object:Gem::Dependency
|
111
|
-
name:
|
84
|
+
name: timecop
|
112
85
|
requirement: !ruby/object:Gem::Requirement
|
113
|
-
none: false
|
114
86
|
requirements:
|
115
|
-
- - ~>
|
87
|
+
- - "~>"
|
116
88
|
- !ruby/object:Gem::Version
|
117
|
-
version:
|
89
|
+
version: 0.7.1
|
118
90
|
type: :development
|
119
91
|
prerelease: false
|
120
92
|
version_requirements: !ruby/object:Gem::Requirement
|
121
|
-
none: false
|
122
93
|
requirements:
|
123
|
-
- - ~>
|
94
|
+
- - "~>"
|
124
95
|
- !ruby/object:Gem::Version
|
125
|
-
version:
|
96
|
+
version: 0.7.1
|
126
97
|
description: Small library for Google URL Shortener API
|
127
98
|
email: jlopes@zigotto.com.br
|
128
99
|
executables: []
|
129
100
|
extensions: []
|
130
101
|
extra_rdoc_files:
|
131
|
-
- README.
|
102
|
+
- README.markdown
|
132
103
|
files:
|
133
|
-
- .travis.yml
|
104
|
+
- ".travis.yml"
|
134
105
|
- Gemfile
|
135
|
-
- Gemfile.lock
|
136
106
|
- MIT-LICENSE
|
137
|
-
- README.
|
107
|
+
- README.markdown
|
138
108
|
- Rakefile
|
139
109
|
- VERSION
|
140
110
|
- googl.gemspec
|
@@ -179,29 +149,25 @@ files:
|
|
179
149
|
homepage: http://github.com/zigotto/googl
|
180
150
|
licenses:
|
181
151
|
- MIT
|
152
|
+
metadata: {}
|
182
153
|
post_install_message:
|
183
154
|
rdoc_options: []
|
184
155
|
require_paths:
|
185
156
|
- lib
|
186
157
|
required_ruby_version: !ruby/object:Gem::Requirement
|
187
|
-
none: false
|
188
158
|
requirements:
|
189
|
-
- -
|
159
|
+
- - ">="
|
190
160
|
- !ruby/object:Gem::Version
|
191
161
|
version: '0'
|
192
|
-
segments:
|
193
|
-
- 0
|
194
|
-
hash: -750896588516717416
|
195
162
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
196
|
-
none: false
|
197
163
|
requirements:
|
198
|
-
- -
|
164
|
+
- - ">="
|
199
165
|
- !ruby/object:Gem::Version
|
200
166
|
version: '0'
|
201
167
|
requirements: []
|
202
168
|
rubyforge_project:
|
203
|
-
rubygems_version:
|
169
|
+
rubygems_version: 2.4.5
|
204
170
|
signing_key:
|
205
|
-
specification_version:
|
171
|
+
specification_version: 4
|
206
172
|
summary: Wrapper for Google URL Shortener API
|
207
173
|
test_files: []
|
data/Gemfile.lock
DELETED
@@ -1,45 +0,0 @@
|
|
1
|
-
GEM
|
2
|
-
remote: http://rubygems.org/
|
3
|
-
specs:
|
4
|
-
addressable (2.2.8)
|
5
|
-
crack (0.1.8)
|
6
|
-
diff-lcs (1.1.3)
|
7
|
-
git (1.2.5)
|
8
|
-
httparty (0.10.0)
|
9
|
-
multi_json (~> 1.0)
|
10
|
-
multi_xml
|
11
|
-
jeweler (1.8.3)
|
12
|
-
bundler (~> 1.0)
|
13
|
-
git (>= 1.2.5)
|
14
|
-
rake
|
15
|
-
rdoc
|
16
|
-
json (1.5.1)
|
17
|
-
multi_json (1.5.0)
|
18
|
-
multi_xml (0.4.4)
|
19
|
-
rake (0.9.2.2)
|
20
|
-
rcov (0.9.9)
|
21
|
-
rdoc (3.12)
|
22
|
-
json (~> 1.4)
|
23
|
-
rspec (2.10.0)
|
24
|
-
rspec-core (~> 2.10.0)
|
25
|
-
rspec-expectations (~> 2.10.0)
|
26
|
-
rspec-mocks (~> 2.10.0)
|
27
|
-
rspec-core (2.10.0)
|
28
|
-
rspec-expectations (2.10.0)
|
29
|
-
diff-lcs (~> 1.1.3)
|
30
|
-
rspec-mocks (2.10.1)
|
31
|
-
webmock (1.8.6)
|
32
|
-
addressable (>= 2.2.7)
|
33
|
-
crack (>= 0.1.7)
|
34
|
-
|
35
|
-
PLATFORMS
|
36
|
-
ruby
|
37
|
-
|
38
|
-
DEPENDENCIES
|
39
|
-
bundler (~> 1.2.3)
|
40
|
-
httparty (~> 0.10.0)
|
41
|
-
jeweler (~> 1.8.3)
|
42
|
-
json (>= 1.4.6)
|
43
|
-
rcov
|
44
|
-
rspec (~> 2.10.0)
|
45
|
-
webmock (~> 1.8.6)
|
data/README.rdoc
DELETED
@@ -1,188 +0,0 @@
|
|
1
|
-
= googl
|
2
|
-
|
3
|
-
Google URL Shortener API in Ruby
|
4
|
-
|
5
|
-
== Continuous Integration
|
6
|
-
|
7
|
-
{<img src="https://secure.travis-ci.org/zigotto/googl.png" />}[http://travis-ci.org/zigotto/googl]
|
8
|
-
|
9
|
-
== Basic Usage
|
10
|
-
|
11
|
-
=== Shorten a long URL
|
12
|
-
|
13
|
-
url = Googl.shorten('http://www.zigotto.com')
|
14
|
-
|
15
|
-
url.short_url
|
16
|
-
=> "http://goo.gl/ump4S"
|
17
|
-
|
18
|
-
url.long_url
|
19
|
-
=> "http://www.zigotto.com/"
|
20
|
-
|
21
|
-
url.qr_code
|
22
|
-
=> "http://goo.gl/ump4S.qr"
|
23
|
-
|
24
|
-
url.info
|
25
|
-
=> "http://goo.gl/ump4S.info"
|
26
|
-
|
27
|
-
=== Expand a short URL
|
28
|
-
|
29
|
-
url = Googl.expand('http://goo.gl/ump4S')
|
30
|
-
|
31
|
-
url.long_url
|
32
|
-
=> "http://www.zigotto.com/"
|
33
|
-
|
34
|
-
== Authentication
|
35
|
-
|
36
|
-
The Google URL Shortener API supports the OAuth and ClientLogin mechanisms for authenticating.
|
37
|
-
|
38
|
-
For shorten requests, each short URL for an authenticated user is unique, and thus will gather unique click statistics. In addition, it shows up on the user's dashboard at http://goo.gl.
|
39
|
-
|
40
|
-
=== ClientLogin
|
41
|
-
|
42
|
-
client = Googl.client('user@gmail.com', 'my_valid_password')
|
43
|
-
|
44
|
-
url = client.shorten('https://github.com/zigotto/googl')
|
45
|
-
url.short_url
|
46
|
-
=> http://goo.gl/DWDfi
|
47
|
-
|
48
|
-
Go to http://goo.gl to see URL statistics.
|
49
|
-
|
50
|
-
=== OAuth
|
51
|
-
|
52
|
-
Google supports three flows of OAuth 2.0
|
53
|
-
|
54
|
-
* The <b>client-side</b> flow for JavaScript applications running in a browser. (TODO)
|
55
|
-
* The <b>server-side</b> flow for web applications with servers that can securely store persistent information.
|
56
|
-
* The <b>native application</b> flow for desktop and mobile applications.
|
57
|
-
|
58
|
-
Today, gem googl support only <b>server-side</b> and <b>native application</b>.
|
59
|
-
|
60
|
-
==== Server-side web applications
|
61
|
-
|
62
|
-
You'll need to register your application with Google to get a <em>client_id</em> and record the <em>redirect_uri</em> you'd like to use.
|
63
|
-
See the Registering[http://code.google.com/apis/accounts/docs/OAuth2.html#Registering] your app with Google section for details on how to register.
|
64
|
-
|
65
|
-
client = Googl::OAuth2.server("client_id", "client_secret", "redirect_uri")
|
66
|
-
|
67
|
-
Redirect your users to
|
68
|
-
|
69
|
-
client.authorize_url
|
70
|
-
|
71
|
-
After the user approves access or chooses not to, we'll redirect to the <em>redirect_uri</em> you passed us and append either an authorization code.
|
72
|
-
|
73
|
-
client.request_access_token(params["code"])
|
74
|
-
client.authorized?
|
75
|
-
=> true
|
76
|
-
|
77
|
-
Now you can get a user's history, shorten url, etc...
|
78
|
-
|
79
|
-
history = client.history
|
80
|
-
history.total_items
|
81
|
-
=> 19
|
82
|
-
|
83
|
-
url = Googl.shorten('https://github.com/zigotto/googl')
|
84
|
-
url.short_url
|
85
|
-
=> http://goo.gl/DWDfi
|
86
|
-
|
87
|
-
==== Native applications
|
88
|
-
|
89
|
-
You'll need to register your application with Google to get a <em>client_id</em> and record the special <em>redirect_uri: urn:ietf:wg:oauth:2.0:oob</em>.
|
90
|
-
See the Registering[http://code.google.com/apis/accounts/docs/OAuth2.html#Registering] your app with Google section for details on how to register.
|
91
|
-
|
92
|
-
When you use this redirect_uri, instead of redirecting the user's browser to a page on your site with an authorization code, Google will display the authorization code or error response in the title of the page and a text field with instructions for the user to copy and paste it in to your application. Your application can either monitor the window title of a web-view or browser window it launches and close it once the authorization code appears or prompt the user to copy and paste the code.
|
93
|
-
|
94
|
-
client = Googl::OAuth2.native("client_id", "client_secret")
|
95
|
-
|
96
|
-
Open a browser or a webview to the OAuth dialog at
|
97
|
-
|
98
|
-
client.authorize_url
|
99
|
-
|
100
|
-
In the native app flow, after the user approves access, we'll display the authorization code in the title of the page and in a text input with instructions for the user to copy and paste the code to your application
|
101
|
-
|
102
|
-
code = "copied code"
|
103
|
-
client.request_access_token(code)
|
104
|
-
client.authorized?
|
105
|
-
=> true
|
106
|
-
|
107
|
-
== Analytics
|
108
|
-
|
109
|
-
Expands a short URL or gets creation time and analytics
|
110
|
-
|
111
|
-
For analytics and additional information to return (using the :projection parameter)
|
112
|
-
|
113
|
-
:full returns the creation timestamp and all available analytics
|
114
|
-
:analytics_clicks returns only click counts
|
115
|
-
:analytics_top_strings returns only top string counts (e.g. referrers, countries, etc)
|
116
|
-
|
117
|
-
=== Get Analytics
|
118
|
-
|
119
|
-
url = Googl.expand('http://goo.gl/DWDfi', :projection => :full)
|
120
|
-
|
121
|
-
url.analytics.all_time.browsers.first.label
|
122
|
-
=> "Chrome"
|
123
|
-
url.analytics.all_time.browsers.first.count
|
124
|
-
=> "11"
|
125
|
-
|
126
|
-
A summary of the click analytics for the short and long URL
|
127
|
-
|
128
|
-
url.analytics
|
129
|
-
|
130
|
-
Analytics details for a particular window of time; counts in this object are of clicks that occurred within the most recent window of this length.
|
131
|
-
|
132
|
-
url.analytics.all_time
|
133
|
-
url.analytics.month
|
134
|
-
url.analytics.week
|
135
|
-
url.analytics.day
|
136
|
-
url.analytics.two_hours
|
137
|
-
|
138
|
-
Number of clicks on this short URL. Replace (*) for all_time, month, week, day or two_hours
|
139
|
-
|
140
|
-
url.analytics.*.short_url_clicks
|
141
|
-
|
142
|
-
Number of clicks on all goo.gl short URLs pointing to this long URL.
|
143
|
-
|
144
|
-
url.analytics.*.long_url_clicks
|
145
|
-
|
146
|
-
Top referring hosts, e.g. "www.google.com"; sorted by (descending) click counts. Only present if this data is available.
|
147
|
-
|
148
|
-
url.analytics.*.referrers
|
149
|
-
|
150
|
-
Top countries (expressed as country codes), e.g. "US" or "DE"; sorted by (descending) click counts.
|
151
|
-
|
152
|
-
url.analytics.*.countries
|
153
|
-
|
154
|
-
Top browsers, e.g. "Chrome"; sorted by (descending) click counts.
|
155
|
-
|
156
|
-
url.analytics.*.browsers
|
157
|
-
|
158
|
-
Top platforms or OSes, e.g. "Windows"; sorted by (descending) click counts.
|
159
|
-
|
160
|
-
url.analytics.*.platforms
|
161
|
-
|
162
|
-
For details, see http://code.google.com/intl/pt-BR/apis/urlshortener/v1/reference.html#resource_url
|
163
|
-
|
164
|
-
== History
|
165
|
-
|
166
|
-
Gets a user's history of shortened URLs. (Authenticated)
|
167
|
-
|
168
|
-
client = Googl.client('user@gmail.com', 'my_valid_password')
|
169
|
-
|
170
|
-
history = client.history
|
171
|
-
history.total_items
|
172
|
-
=> 19
|
173
|
-
|
174
|
-
A list of URL.
|
175
|
-
|
176
|
-
history.items
|
177
|
-
|
178
|
-
Analytics details for all items
|
179
|
-
|
180
|
-
history = client.history(:projection => :analytics_clicks)
|
181
|
-
|
182
|
-
== Installation
|
183
|
-
|
184
|
-
gem install googl
|
185
|
-
|
186
|
-
== License
|
187
|
-
|
188
|
-
MIT License. Copyright 2011 Zigotto®. http://www.zigotto.com.br
|