google_client 0.1.0

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