google_client 0.1.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.
data/.rspec ADDED
@@ -0,0 +1 @@
1
+ --color --format doc
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source 'http://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in google_client.gemspec
4
+ gemspec
data/README.md ADDED
@@ -0,0 +1,92 @@
1
+
2
+ This gem can be used to get access to a specific set of Google APIs.
3
+
4
+ # Installation
5
+
6
+ gem install google_client
7
+
8
+ # Features
9
+
10
+ * Rails Engine to launch OAuth mechanism
11
+ * Refresh OAuth token
12
+ * Google Calendar
13
+ * Fetch the list of user calendars
14
+ * Create a new calendar
15
+ * Retrieve a specific calendar
16
+ * Fetch calendar events, filtering by specific event id, time interval (and more coming)
17
+ * Delete calendar
18
+ * Create event
19
+ * Delete event
20
+ * Google Contacts
21
+ * Fetch user contacts
22
+
23
+ # Getting started
24
+
25
+ Any request that may be done on behalf of the user needs a valid authentication token:
26
+
27
+ require "gogole_client"
28
+ user = GoogleClient::User.new "user-authentication-token"
29
+
30
+ # Get user contacts
31
+ contacts = user.contacts
32
+
33
+ # Get user calendars
34
+ calendars = user.calendars
35
+
36
+ # Get a specific user calendar
37
+ calendar = user.calendars("<calendar-id>")
38
+
39
+ # Fetch calendar events
40
+ events = calendar.events
41
+
42
+ # Create calendar
43
+ calendar = user.create_calendar({:title => "my-new-calendar",
44
+ :details => "Hello world",
45
+ :timezone => "Spain", :location => "Barcelona"})
46
+
47
+ # ...
48
+
49
+ Take a look on the [user.rb](blob/master/lib/google_client/user.rb) file to check the available methods
50
+
51
+ # Roadmap
52
+
53
+ * Enhance filters
54
+ * Handle contacts
55
+ * Create a new contact
56
+ * Delete an existing contact
57
+ * Update a contact
58
+ * Add XML format support. Currently (except the OAuth requests) all the requests/responses are JSON encoded. It may be nice to add support for XML too.
59
+
60
+ # Note on Patches/Pull Requests
61
+
62
+ * Fork the project
63
+ * Make your feature addition or bug fix.
64
+ * Add tests for it. This is important so I don't break it in a future version unintentionally.
65
+ * Commit, do not mess with rakefile, version, or history.
66
+ * If you want to have your own version, that is fine but bump version in a commit by itself so I can ignore when I pull
67
+ * Send me a pull request. Bonus points for topic branches.
68
+
69
+ # License
70
+
71
+ The MIT License
72
+
73
+ Copyright (c) 2011 Juan de Bravo
74
+
75
+ Permission is hereby granted, free of charge, to any person obtaining
76
+ a copy of this software and associated documentation files (the
77
+ 'Software'), to deal in the Software without restriction, including
78
+ without limitation the rights to use, copy, modify, merge, publish,
79
+ distribute, sublicense, and/or sell copies of the Software, and to
80
+ permit persons to whom the Software is furnished to do so, subject to
81
+ the following conditions:
82
+
83
+ The above copyright notice and this permission notice shall be
84
+ included in all copies or substantial portions of the Software.
85
+
86
+ THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND,
87
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
88
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
89
+ IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
90
+ CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
91
+ TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
92
+ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/Rakefile ADDED
@@ -0,0 +1,2 @@
1
+ #!/usr/bin/env rake
2
+ require "bundler/gem_tasks"
@@ -0,0 +1,79 @@
1
+
2
+ class GoogleClientController < ApplicationController
3
+
4
+ # parameters required in both OAuth steps
5
+ DEFAULT_PARAMS = {
6
+ :client_id => Rails.application.config.google_client.client_id
7
+ }
8
+
9
+ # OAuth step1: redirect to Google endpoint with the application idenfitifer and the redirect uri
10
+ def index
11
+ _params = {
12
+ :redirect_uri => Rails.application.config.google_client.redirect_uri,
13
+ :response_type => "code",
14
+ :scope => Rails.application.config.google_client.scope,
15
+ }
16
+ _params.merge!(DEFAULT_PARAMS)
17
+
18
+ redirect_to connection.create_uri("/o/oauth2/auth", _params).to_s
19
+ end
20
+
21
+ # OAuth step2: retrieve the code from Google, ask for a valid access token and
22
+ # forward to the user defined uri
23
+ def oauth2callback
24
+ # This code is retrieved from Google
25
+
26
+ _params = {
27
+ :redirect_uri => Rails.application.config.google_client.redirect_uri,
28
+ :client_secret => Rails.application.config.google_client.client_secret,
29
+ :grant_type => "authorization_code",
30
+ :code => params[:code]
31
+ }
32
+
33
+ _params.merge!(DEFAULT_PARAMS)
34
+
35
+ response = connection.post "/o/oauth2/token", _params
36
+
37
+ if response.code.to_s.eql?("200")
38
+ response = ActiveSupport::JSON.decode(response.body)
39
+
40
+ if Rails.application.config.google_client.forward_action.nil? or !Rails.application.config.google_client.forward_action.is_a?(String)
41
+ raise GoogleClient::Error.new("Invalid forward_action value")
42
+ end
43
+
44
+ url = Rails.application.config.google_client.forward_action.split("#")
45
+
46
+ url.length == 2 or raise GoogleClient::Error.new("Invalid forward_action value")
47
+
48
+ @data = response
49
+
50
+ redirect_to ({
51
+ :controller => url.first,
52
+ :action => url.last,
53
+ :access_token => response["access_token"],
54
+ :token_type => response["token_type"],
55
+ :expires_in => response["expires_in"],
56
+ :refresh_token => response["refresh_token"]
57
+ })
58
+
59
+ else
60
+ logger.error "Error #{response.code} while accessing to Google #{response.body}"
61
+ raise RuntimeError, "Unable to access to Google"
62
+ end
63
+ end
64
+
65
+ def show
66
+ @data = {:access_token => params[:access_token],
67
+ :token_type => params[:token_type],
68
+ :expires_in => params[:expires_in],
69
+ :refresh_token => params[:refresh_token]}
70
+ end
71
+
72
+
73
+ private
74
+
75
+ def connection
76
+ @connection ||= GoogleClient::HttpConnection.new("https://accounts.google.com")
77
+ end
78
+
79
+ end
@@ -0,0 +1,54 @@
1
+ <style >
2
+ #oauth {
3
+ margin-left: 10%;
4
+ width: 80%;
5
+ font-size: 1em;
6
+ font-family: 'Verdana', 'Helvetica', 'Arial';
7
+ }
8
+
9
+ #oauth h2{
10
+ margin-top: 50px;
11
+ color: #47C3D3;
12
+ }
13
+
14
+ #oauth dt{
15
+ float: left;
16
+ font-weight: bold;
17
+ width: 30%;
18
+ }
19
+
20
+ #oauth dd{
21
+ width: 70%;
22
+ }
23
+
24
+ #oauth p{
25
+ margin-top: 50px;
26
+ }
27
+
28
+ #oauth .email{
29
+ float: right;
30
+ }
31
+
32
+ </style>
33
+
34
+ <div id="oauth">
35
+ <h2>OAuth Engine</h2>
36
+ <p><h4>This is the data retrieved as result of the OAuth process:</h4></p>
37
+ <dl>
38
+ <dt>User access token</dt>
39
+ <dd><%=@data[:access_token]%></dd>
40
+ <dt>Token type</dt>
41
+ <dd><%=@data[:token_type]%></dd>
42
+ <dt>Expires in</dt>
43
+ <dd><%=@data[:expires_in]%></dd>
44
+ <dt>Refresh token</dt>
45
+ <dd><%=@data[:refresh_token]%></dd>
46
+ </dl>
47
+ <p>
48
+ So now that you have it working, please go to the configuration initializer and set up a cool forward page instead of this :)
49
+ </p>
50
+ <p class="email">
51
+ juandebravo at gmail dot com
52
+ </p>
53
+
54
+ </div>
data/config/routes.rb ADDED
@@ -0,0 +1,9 @@
1
+ Rails.application.routes.draw do
2
+
3
+ get "google_client/index"
4
+
5
+ get "google_client/oauth2callback"
6
+
7
+ get "google_client/show"
8
+
9
+ end
@@ -0,0 +1,25 @@
1
+ # -*- encoding: utf-8 -*-
2
+ require File.expand_path('../lib/google_client/version', __FILE__)
3
+
4
+ Gem::Specification.new do |gem|
5
+ gem.authors = ["juandebravo"]
6
+ gem.email = ["juandebravo@gmail.com"]
7
+ gem.summary = %q{Ease way to get access to Google API.}
8
+ gem.description = %q{This gem is a wrapper on top of the Google API that allows a developer to handle calendars, events, contacts on behalf of the user.}
9
+ gem.homepage = "http://www.github.com/juandebravo/google_client"
10
+
11
+ gem.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
12
+ gem.files = `git ls-files`.split("\n")
13
+ gem.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
14
+ gem.name = "google_client"
15
+ gem.require_paths = ["lib"]
16
+ gem.version = GoogleClient::VERSION
17
+
18
+ gem.add_dependency("rest-client")
19
+ gem.add_dependency("addressable")
20
+
21
+ gem.add_development_dependency("rake")
22
+ gem.add_development_dependency("rspec")
23
+ gem.add_development_dependency("webmock")
24
+
25
+ end
@@ -0,0 +1,26 @@
1
+ require 'json'
2
+ require "google_client/version"
3
+
4
+ require 'google_client/engine' if defined?(Rails)
5
+
6
+ module GoogleClient
7
+
8
+ autoload :AuthenticationError, 'google_client/error'
9
+ autoload :BadRequestError , 'google_client/error'
10
+ autoload :Calendar , 'google_client/calendar'
11
+ autoload :Contact , 'google_client/contact'
12
+ autoload :Error , 'google_client/error'
13
+ autoload :Event , 'google_client/event'
14
+ autoload :Format , 'google_client/format'
15
+ autoload :HttpConnection , 'google_client/http_connection'
16
+ autoload :NotFoundError , 'google_client/error'
17
+ autoload :Profile , 'google_client/profile'
18
+ autoload :User , 'google_client/user'
19
+
20
+ class << self
21
+ def create_client(oauth_credentials)
22
+ User.new(oauth_credentials)
23
+ end
24
+ end
25
+
26
+ end
@@ -0,0 +1,181 @@
1
+ module GoogleClient
2
+ class Calendar
3
+
4
+ BASE_URL = "https://www.google.com/calendar/feeds"
5
+
6
+ include Format
7
+
8
+ attr_accessor :id
9
+ attr_accessor :title
10
+ attr_accessor :details
11
+ attr_accessor :timezone
12
+ attr_accessor :location
13
+
14
+ def initialize(params)
15
+ @user = params[:user]
16
+ @user.nil? or @connection = @user.connection
17
+ @id = params[:calendar_id] || params[:id]
18
+ @title = params[:title]
19
+ @details = params[:details]
20
+ @timezone = params[:timezone]
21
+ @location = params[:location]
22
+ @json_mode = true
23
+ end
24
+
25
+ def to_s
26
+ "#{self.class.name} => { id: #{@id}, title: #{@title}, :timezone => #{@timezone}, :location => #{@location} }"
27
+ end
28
+
29
+ def connection
30
+ @connection or raise RuntimeError.new "Http connection not established"
31
+ end
32
+
33
+ ##
34
+ # Save the Calendar in the server
35
+ #
36
+ # @return Event instance
37
+ def save
38
+ if @id.nil?
39
+ data = decode_response connection.post("/calendar/feeds/default/owncalendars/full", {:data => self.to_hash})
40
+ self.id = data["data"]["id"].split("full/").last
41
+ else
42
+ data = decode_response connection.put("/calendar/feeds/default/owncalendars/full/#{@id}", {:apiVersion => "2.3", :data => self.to_hash})
43
+ end
44
+ self
45
+ end
46
+
47
+ def delete
48
+ @id.nil? and raise Error.new "calendar cannot be deleted if has not an unique identifier"
49
+ connection.delete("/calendar/feeds/default/owncalendars/full/#{@id}")
50
+ true
51
+ end
52
+
53
+ def to_hash
54
+ {
55
+ :title => @title,
56
+ :details => @details,
57
+ :timeZone => @timezone,
58
+ :location => @location
59
+ }.delete_if{|k,v| v.nil?}
60
+ end
61
+
62
+ ##
63
+ # Fetch a set of the events from the calendar
64
+ # ==== Parameters
65
+ # * *params*: Hash
66
+ # ** *id*: optional, specific event identifier. If present, no other parameter will be considered
67
+ # ** *from*: optional, if *to* is present and from is not, all events starting till *to* will be fetched
68
+ # ** *to*: optional, if *from* is present and to is not, all events starting after *from* will be fetched
69
+ #
70
+ # ==== Return
71
+ # * Nil if no event matches the query parameters.
72
+ # * Event instance if only one Event matches the query parameter.
73
+ # * An array of Event instances if more than one Event matches the query parameter.
74
+ #
75
+ def events(args = nil)
76
+ events = if args.nil? || args.eql?(:all)
77
+ events = decode_response connection.get "/calendar/feeds/#{id}/private/full"
78
+ events = events["feed"]["entry"]
79
+ events.map{ |event| Event.build_event(event, self)}
80
+ elsif args.is_a?(String)
81
+ raise Error.new "Unable to fetch a Event by id"
82
+ #Event.new({:id => args, :calendar => self}).fetch
83
+ elsif args.is_a?(Hash)
84
+ if args.is_a?(Hash) && args.has_key?(:id)
85
+ Event.new({:id => args[:id], :calendar => self}).fetch
86
+ else
87
+ params = { "start-min" => args[:from],
88
+ "start-max" => args[:to]}
89
+ events = decode_response connection.get "/calendar/feeds/#{id}/private/full", params
90
+ events = events["feed"]["entry"]
91
+ events = events.nil? ? [] : events.map{ |event| Event.build_event(event, self)}
92
+ end
93
+ else
94
+ raise ArgumentError.new "Invalid argument type #{args.class}"
95
+ end
96
+
97
+ end
98
+
99
+ ##
100
+ # Fetch forthcoming events in the folowing *time* minutes
101
+ def forthcoming_events(time = 3600)
102
+ events({:from => Time.now.strftime("%Y-%m-%dT%k:%M:%S").concat(timezone).gsub(/ /,"0"),
103
+ :to => (Time.now + time).strftime("%Y-%m-%dT%k:%M:%S").concat(timezone).gsub(/ /,"0")})
104
+ end
105
+
106
+ def create_event(params = {})
107
+ event = if block_given?
108
+ Event.create(params.merge({:calendar => self}), &Proc.new)
109
+ else
110
+ Event.create(params.merge({:calendar => self}))
111
+ end
112
+ event.save
113
+ end
114
+
115
+ def delete_event(event_id)
116
+ end
117
+
118
+ def fetch
119
+ data = decode_response connection.get "/calendar/feeds/default/owncalendars/full/#{id}"
120
+ self.class.build_calendar data["entry"], @user
121
+ end
122
+
123
+ # Helper to get the Timezone in rfc3339 format
124
+ def timezone
125
+ @@timezone ||= (
126
+ timezone = Time.now.strftime("%z")
127
+ timezone[0..2].concat(":").concat(timezone[3..-1])
128
+ )
129
+ end
130
+
131
+ class << self
132
+ def create(params)
133
+ calendar = if block_given?
134
+ new(params, &Proc.new)
135
+ else
136
+ new(params)
137
+ end
138
+ end
139
+
140
+ def build_calendar(data, user = nil)
141
+
142
+ id = begin
143
+ data["id"]["$t"].split("full/").last
144
+ rescue
145
+ nil
146
+ end
147
+
148
+ details = begin
149
+ data["summary"]["$t"]
150
+ rescue
151
+ ""
152
+ end
153
+ title = begin
154
+ data["title"]["$t"]
155
+ rescue
156
+ ""
157
+ end
158
+
159
+ timezone = begin
160
+ data["gCal$timezone"]["value"]
161
+ rescue
162
+ nil
163
+ end
164
+
165
+ location = begin
166
+ data["gd$where"][0]["valueString"]
167
+ rescue
168
+ nil
169
+ end
170
+
171
+ Calendar.new({:id => id,
172
+ :user => user,
173
+ :title => title,
174
+ :details => details,
175
+ :location => location,
176
+ :timezone => timezone})
177
+ end
178
+ end
179
+ end
180
+
181
+ end