glass 0.0.1.1 → 0.0.1.2

Sign up to get free protection for your applications and to get access to all the features.
data/.gitignore CHANGED
File without changes
data/Gemfile CHANGED
File without changes
File without changes
data/README.md CHANGED
@@ -4,6 +4,12 @@ This Gem is meant to help you quickly building application for Google Glass than
4
4
 
5
5
  This is totally a beginning. Nothing is done.
6
6
 
7
+ This gem is using google/api-client.
8
+
9
+ If you think google/api-client is b***s*** this gem is what you need.
10
+
11
+ This gem is using Redis to store clients credentials.
12
+
7
13
  ## Installation
8
14
 
9
15
  Add this line to your application's Gemfile:
@@ -20,7 +26,105 @@ Or install it yourself as:
20
26
 
21
27
  ## Usage
22
28
 
23
- TODO: Write usage instructions here
29
+ Setup Credential
30
+
31
+ ```ruby
32
+ require 'glass'
33
+ Glass::Mirror.client_id = ENV['GLASS_CLIENT_ID']
34
+ Glass::Mirror.client_secret = ENV['GLASS_CLIENT_SECRET']
35
+ Glass::Mirror.redirect_uri = ENV['GLASS_REDIRECT_URI']
36
+ Glass::Mirror.scopes = [ Add requested scopes]
37
+ # Default is 'https://www.googleapis.com/auth/drive.file',
38
+ # 'https://www.googleapis.com/auth/userinfo.profile',
39
+ ```
40
+
41
+ You are a Glassware Explorer Now !
42
+ Get a way to get the authorization code from user app install. I don't know how for now. If you find let me know !
43
+ And do the OAuth trick
44
+ ```ruby
45
+ ok_glass = Glass::Mirror.build_with_code(authorization_code)
46
+ ```
47
+ ### Token Persistence
48
+
49
+ As in OAuth2.0, Google API issue a Refresh_Token the first time you Authorize the client, so you can get new token later.
50
+ You need to store this token. Out of the box Glass's Gem use Redis to do it.
51
+
52
+ Glass use a namespace on your redis. So don't bother about this (even if you can specify one too).
53
+
54
+ By default, Glass use local redis store (localhost:6379).
55
+
56
+ You can specify the Redis store this way :
57
+
58
+ For Heroku/RedisToGo users
59
+
60
+ ```ruby
61
+ Glass::Mirror.redis = ENV["REDISTOGO_URL"]
62
+ ```
63
+
64
+
65
+ ## Overview
66
+
67
+ ### Timeline
68
+
69
+ Let's say you want to tell "Hi" to the user.
70
+ Build it and send it.
71
+
72
+ ```ruby
73
+ item = Glass::TimelineItem.new()
74
+ item.text="Hi"
75
+ ok_glass.insert(item)
76
+ ```
77
+
78
+ ### Subscriptions
79
+
80
+ Let's say you want to subscribe to location update.
81
+ You need to provide a call_back_url, on wich Google will post the datas.
82
+
83
+ ```ruby
84
+ subscription = Glass::Subscription.new()
85
+ subscription.collection="location"
86
+ subscription.call_back_url="https://glassware.herokuapp.com/location_update"
87
+ ok_glass.insert(subscription)
88
+ ```
89
+
90
+ ### Location
91
+
92
+ There is a quicker way to get Location update
93
+ ```ruby
94
+ Glass::Location.subscribe(ok_glass,call_back_url)
95
+ ```
96
+
97
+
98
+
99
+ ## Going Further
100
+
101
+ As you are a Glassware Explorer you can explore yourself. Access the google/api-client Client this way :
102
+ ```ruby
103
+ ok_glass.client # Get the discovered Google::API on wich you can make request
104
+ ```
105
+
106
+ Have fun ! And please, show me any use. Just send me a tweet : @TheDamFr
107
+
108
+
109
+ ### Bypassing Redis
110
+
111
+ You can bypass Redis. I don't want to know why you would, but you can do it easily.
112
+
113
+ Just do :
114
+ ```ruby
115
+ Glass::Mirror.no_redis = true # Glass are so sad !
116
+ ```
117
+
118
+ Now you need to handle the storage yourself !
119
+
120
+ ```ruby
121
+ credentials = Glass::Mirror.get_credentials(authorization_code)
122
+
123
+ # Store Credentials !
124
+
125
+ ok_glass = Glass::Mirror.build_client(credentials)
126
+ ```
127
+ That's okay ! (for now)
24
128
 
25
129
  ## Contributing
26
130
 
data/Rakefile CHANGED
File without changes
@@ -21,4 +21,7 @@ Gem::Specification.new do |spec|
21
21
  spec.add_development_dependency "bundler", "~> 1.3"
22
22
  spec.add_development_dependency "rake"
23
23
  spec.add_runtime_dependency "google-api-client"
24
+ spec.add_runtime_dependency "redis"
25
+ spec.add_runtime_dependency "redis-namespace"
26
+ spec.add_runtime_dependency "httparty"
24
27
  end
@@ -1,18 +1,296 @@
1
1
  require "glass/version"
2
+ require 'redis'
3
+ require 'redis-namespace'
4
+ require 'glass/config'
5
+ require 'google/api_client'
6
+ require 'json'
2
7
 
3
8
  module Glass
4
9
 
10
+ MIRROR= "mirror"
11
+
12
+ INSERT= "INSERT"
13
+ UPDATE= "UPDATE"
14
+ DELETE= "DELETE"
15
+
16
+
5
17
  class Mirror
6
- attr_accessor :scopes, :client_id, :redirect_uri, :cient_secret
7
- @@client_id
8
- @@cient_secret
9
- @@redirect_uri
10
- @@scopes = [
11
- 'https://www.googleapis.com/auth/drive.file',
12
- 'https://www.googleapis.com/auth/userinfo.profile',
13
- # Add other requested scopes.
14
- ]
15
18
 
19
+
20
+ class << self
21
+ attr_accessor :scopes, :client_id, :redirect_uri, :client_secret
22
+ @client_id
23
+ @client_secret
24
+ @redirect_uri
25
+ @scopes = [
26
+ 'https://www.googleapis.com/auth/drive.file',
27
+ 'https://www.googleapis.com/auth/userinfo.profile',
28
+ # Add other requested scopes.
29
+ ]
30
+ end
31
+
32
+
33
+ @@config = Config.new()
34
+
35
+ attr_accessor :client
36
+ @client
37
+
38
+ def self.hello_world
39
+ "Hello World!"
40
+ end
41
+
42
+ # Returns the current Redis connection. If none has been created, will
43
+ # create a new one.
44
+ def self.redis
45
+ @@config.redis
46
+ end
47
+
48
+ def self.redis= val
49
+ @@config.redis = val
50
+ end
51
+
52
+ def self.redis_id
53
+ @@config.redis_id
54
+ end
55
+
56
+ def self.no_redis= val
57
+ @@config.no_redis=val
58
+ end
59
+
60
+ ##
61
+ # Retrieved stored credentials for the provided user ID.
62
+ #
63
+ # @param [String] user_id
64
+ # User's ID.
65
+ # @return [Signet::OAuth2::Client]
66
+ # Stored OAuth 2.0 credentials if found, nil otherwise.
67
+ #
68
+ def self.get_stored_credentials(user_id)
69
+ unless @@config.no_redis
70
+ hash = Redis.get(user_id)
71
+ client = Google::APIClient.new
72
+ client.authorization.dup
73
+ client.update_token!(hash)
74
+ end
75
+ end
76
+
77
+ ##
78
+ # Store OAuth 2.0 credentials in the application's database.
79
+ #
80
+ # @param [String] user_id
81
+ # User's ID.
82
+ # @param [Signet::OAuth2::Client] credentials
83
+ # OAuth 2.0 credentials to store.
84
+ #
85
+ def self.store_credentials(user_id, credentials)
86
+ unless @@config.no_redis
87
+ hash = Hash.new()
88
+ hash[:access_token] = credentials.access_token
89
+ hash[:refresh_token] = credentials.refresh_token
90
+ hash[:expires_in] = credentials.expires_in
91
+ hash[:issued_at] = credentials.issued_at
92
+
93
+ Redis.set(user_id, hash)
94
+ end
95
+ end
96
+
97
+ ##
98
+ # Exchange an authorization code for OAuth 2.0 credentials.
99
+ #
100
+ # @param [String] auth_code
101
+ # Authorization code to exchange for OAuth 2.0 credentials.
102
+ # @return [Signet::OAuth2::Client]
103
+ # OAuth 2.0 credentials.
104
+ #
105
+ def self.exchange_code(authorization_code)
106
+ client = Google::APIClient.new
107
+ client.authorization.client_id = client_id
108
+ client.authorization.client_secret = client_secret
109
+ client.authorization.code = authorization_code
110
+ client.authorization.redirect_uri = redirect_uri
111
+
112
+ begin
113
+ client.authorization.fetch_access_token!
114
+ return client.authorization
115
+ rescue Signet::AuthorizationError
116
+ raise CodeExchangeError.new(nil)
117
+ end
118
+ end
119
+
120
+ ##
121
+ # Send a request to the UserInfo API to retrieve the user's information.
122
+ #
123
+ # @param [Signet::OAuth2::Client] credentials
124
+ # OAuth 2.0 credentials to authorize the request.
125
+ # @return [Google::APIClient::Schema::Oauth2::V2::Userinfo]
126
+ # User's information.
127
+ #
128
+ def self.get_user_info(credentials)
129
+ client = Google::APIClient.new
130
+ client.authorization = credentials
131
+ oauth2 = client.discovered_api('oauth2', 'v2')
132
+ result = client.execute!(:api_method => oauth2.userinfo.get)
133
+ user_info = nil
134
+ if result.status == 200
135
+ user_info = result.data
136
+ else
137
+ puts "An error occurred: #{result.data['error']['message']}"
138
+ end
139
+ if user_info != nil && user_info.id != nil
140
+ return user_info
141
+ end
142
+ raise NoUserIdError, "Unable to retrieve the user's Google ID."
143
+ end
144
+
145
+ ##
146
+ # Retrieve authorization URL.
147
+ #
148
+ # @param [String] user_id
149
+ # User's Google ID.
150
+ # @param [String] state
151
+ # State for the authorization URL.
152
+ # @return [String]
153
+ # Authorization URL to redirect the user to.
154
+ #
155
+ def self.get_authorization_url(user_id, state)
156
+ client = Google::APIClient.new
157
+ client.authorization.client_id = client_id
158
+ client.authorization.redirect_uri = redirect_uri
159
+ client.authorization.scope = scopes
160
+
161
+ return client.authorization.authorization_uri(
162
+ :options => {
163
+ :approval_prompt => :force,
164
+ :access_type => :offline,
165
+ :user_id => user_id,
166
+ :state => state
167
+ }).to_s
168
+ end
169
+
170
+ ##
171
+ # Retrieve credentials using the provided authorization code.
172
+ #
173
+ # This function exchanges the authorization code for an access token and queries
174
+ # the UserInfo API to retrieve the user's Google ID.
175
+ # If a refresh token has been retrieved along with an access token, it is stored
176
+ # in the application database using the user's Google ID as key.
177
+ # If no refresh token has been retrieved, the function checks in the application
178
+ # database for one and returns it if found or raises a NoRefreshTokenError
179
+ # with an authorization URL to redirect the user to.
180
+ #
181
+ # @param [String] auth_code
182
+ # Authorization code to use to retrieve an access token.
183
+ # @param [String] state
184
+ # State to set to the authorization URL in case of error.
185
+ # @return [Signet::OAuth2::Client]
186
+ # OAuth 2.0 credentials containing an access and refresh token.
187
+ #
188
+ def self.get_credentials(authorization_code, state="OAuth Failed")
189
+ user_id = ''
190
+ begin
191
+ credentials = exchange_code(authorization_code)
192
+ user_info = get_user_info(credentials)
193
+ user_id = user_info.id
194
+ if credentials.refresh_token != nil
195
+ store_credentials(user_id, credentials)
196
+ return credentials
197
+ else
198
+ credentials = get_stored_credentials(user_id)
199
+ if credentials != nil && credentials.refresh_token != nil
200
+ return credentials
201
+ end
202
+ end
203
+ rescue CodeExchangeError => error
204
+ print 'An error occurred during code exchange.'
205
+ # Drive apps should try to retrieve the user and credentials for the current
206
+ # session.
207
+ # If none is available, redirect the user to the authorization URL.
208
+ error.authorization_url = get_authorization_url(user_id, state)
209
+ raise error
210
+ rescue NoUserIdError
211
+ print 'No user ID could be retrieved.'
212
+ end
213
+
214
+ authorization_url = get_authorization_url(user_id, state)
215
+ raise NoRefreshTokenError.new(authorization_url)
216
+ end
217
+
218
+
219
+ ##
220
+ # Build a Mirror client instance.
221
+ #
222
+ # @param [Signet::OAuth2::Client] credentials
223
+ # OAuth 2.0 credentials.
224
+ # @return [Google::APIClient]
225
+ # Client instance
226
+ def self.build_client(credentials)
227
+ m = Mirror.new()
228
+ m.client = Google::APIClient.new
229
+ m.client.authorization = credentials
230
+ m.client = m.client.discovered_api('mirror', 'v1')
231
+ m
232
+ end
233
+
234
+ def self.build_with_code(authorization_code)
235
+ return build_client(get_credentials(authorization_code))
236
+ end
237
+
238
+ def insert(item)
239
+ item.insert(client)
240
+ end
241
+
242
+ def delete(item)
243
+ item.delete(client)
244
+ end
245
+
246
+ end
247
+
248
+ ##
249
+ # Error raised when an error occurred while retrieving credentials.
250
+ #
251
+ class GetCredentialsError < StandardError
252
+ ##
253
+ # Initialize a NoRefreshTokenError instance.
254
+ #
255
+ # @param [String] authorize_url
256
+ # Authorization URL to redirect the user to in order to in order to request
257
+ # offline access.
258
+ #
259
+ def initialize(authorization_url)
260
+ @authorization_url = authorization_url
261
+ end
262
+
263
+ def authorization_url=(authorization_url)
264
+ @authorization_url = authorization_url
265
+ end
266
+
267
+ def authorization_url
268
+ return @authorization_url
269
+ end
270
+ end
271
+
272
+ ##
273
+ # Error raised when a code exchange has failed.
274
+ #
275
+ class CodeExchangeError < GetCredentialsError
276
+ end
277
+
278
+ ##
279
+ # Error raised when no refresh token has been found.
280
+ #
281
+ class NoRefreshTokenError < GetCredentialsError
282
+ end
283
+
284
+ ##
285
+ # Error raised when no user ID could be retrieved.
286
+ #
287
+ class NoUserIdError < StandardError
16
288
  end
17
289
 
290
+
18
291
  end
292
+
293
+ require 'glass/contacts/contact'
294
+ require 'glass/locations/location'
295
+ require 'glass/subscriptions/subscription'
296
+ require 'glass/timeline/timeline_item'
@@ -0,0 +1,65 @@
1
+ module Glass
2
+ class Config
3
+
4
+
5
+ attr_accessor :no_redis
6
+
7
+ ##
8
+ # Accepts:
9
+ # 1. A 'hostname:port' String
10
+ # 2. A 'hostname:port:db' String (to select the Redis db)
11
+ # 3. A 'hostname:port/namespace' String (to set the Redis namespace)
12
+ # 4. A Redis URL String 'redis://host:port'
13
+ # 5. An instance of `Redis`, `Redis::Client`, `Redis::DistRedis`,
14
+ # or `Redis::Namespace`.
15
+ def redis=(server)
16
+ if @no_redis then return nil end
17
+ return if server == "" or server.nil?
18
+
19
+ @redis = case server
20
+ when String
21
+ if server['redis://']
22
+ redis = Redis.connect(:url => server, :thread_safe => true)
23
+ else
24
+ server, namespace = server.split('/', 2)
25
+ host, port, db = server.split(':')
26
+
27
+ redis = Redis.new(
28
+ :host => host,
29
+ :port => port,
30
+ :db => db,
31
+ :thread_safe => true
32
+ )
33
+ end
34
+ Redis::Namespace.new(namespace || :glass, :redis => redis)
35
+ when Redis::Namespace, Redis::Distributed
36
+ server
37
+ when Redis
38
+ Redis::Namespace.new(:glass, :redis => server)
39
+ end
40
+ end
41
+
42
+ def redis
43
+ if @no_redis then return nil end
44
+ return @redis if @redis
45
+ self.redis = Redis.respond_to?(:connect) ? Redis.connect : "localhost:6379"
46
+ self.redis
47
+ end
48
+
49
+ def redis_id
50
+ if @no_redis then return nil end
51
+ # support 1.x versions of redis-rb
52
+ if redis.respond_to?(:server)
53
+ redis.server
54
+ elsif redis.respond_to?(:nodes) # distributed
55
+ redis.nodes.map(&:id).join(', ')
56
+ else
57
+ redis.client.id
58
+ end
59
+ end
60
+
61
+ @no_redis
62
+
63
+
64
+ end
65
+ end
@@ -0,0 +1,66 @@
1
+
2
+ module Glass
3
+ CONTACT="contact"
4
+
5
+ # A person or group that can be used as a creator or a contact.
6
+ #
7
+ class Contact
8
+
9
+ class << self
10
+ # The type of resource. This is always mirror#contact.
11
+ #
12
+ attr_accessor :kind
13
+ end
14
+
15
+ @@kind = MIRROR+"#"+CONTACT
16
+
17
+ # The ID of the application that created this contact.
18
+ # This is populated by the API
19
+ attr_reader :source
20
+ @source
21
+
22
+ # An ID for this contact.
23
+ # This is generated by the application and is treated as an opaque token.
24
+ #
25
+ attr_reader :id
26
+ @id
27
+
28
+ # The name to display for this contact.
29
+ #
30
+ attr_accessor :displayName
31
+ @displayName
32
+
33
+ # Set of image URLs to display for a contact.
34
+ # Most contacts will have a single image, but a "group" contact may include up to 8 image URLs and they will be resized and cropped into a mosaic on the client.
35
+ #
36
+ attr_accessor :imageUrls
37
+ @imageUrls=[]
38
+
39
+ # The type for this contact. This is used for sorting in UIs. Allowed values are:
40
+ # INDIVIDUAL - Represents a single person. This is the default.
41
+ # GROUP - Represents more than a single person.
42
+ #
43
+ attr_accessor :type
44
+ @type
45
+
46
+ # A list of MIME types that a contact supports.
47
+ # The contact will be shown to the user if any of its acceptTypes matches any of the types of the attachments on the item.
48
+ # If no acceptTypes are given, the contact will be shown for all items.
49
+ #
50
+ attr_accessor :acceptTypes
51
+ @acceptTypes=[]
52
+
53
+ # Primary phone number for the contact.
54
+ # This can be a fully-qualified number, with country calling code and area code, or a local number.
55
+ #
56
+ attr_accessor :phoneNumber
57
+ @phoneNumber
58
+
59
+ # Priority for the contact to determine ordering in a list of contacts.
60
+ # Contacts with higher priorities will be shown before ones with lower priorities.
61
+ #
62
+ attr_accessor :priority
63
+ @priority
64
+
65
+ end
66
+ end
@@ -0,0 +1,64 @@
1
+ require 'glass/subscriptions/subscription.rb'
2
+ module Glass
3
+ LOCATION="location"
4
+
5
+ # A geographic location that can be associated with a timeline item.
6
+ #
7
+ class Location
8
+
9
+ class << self
10
+ # The type of resource. This is always mirror#location.
11
+ #
12
+ attr_reader :kind
13
+
14
+ def subscribe(mirror, call_back_url)
15
+ s = Subscription.new()
16
+ s.mirror=mirror
17
+ s.callbackUrl=call_back_url
18
+ s.collection=Subscription::LOCATION
19
+ s.operation << UPDATE
20
+ s.insert
21
+ end
22
+ end
23
+
24
+ @@kind
25
+
26
+
27
+ # The ID of the location.
28
+ #
29
+ attr_reader :id
30
+ @id
31
+
32
+ # The time at which this location was captured, formatted according to RFC 3339.
33
+ #
34
+ attr_reader :timestamp
35
+ @timestamp
36
+
37
+ # The latitude, in degrees.
38
+ #
39
+ attr_accessor :latitude
40
+ @latitude
41
+
42
+ # The longitude, in degrees.
43
+ attr_accessor :longitude
44
+ @longitude
45
+
46
+ # The accuracy of the location fix in meters.
47
+ #
48
+ attr_accessor :accuracy
49
+ @accuracy
50
+
51
+ # The name to be displayed. This may be a business name or a user-defined place, such as "Home".
52
+ #
53
+ attr_accessor :displayName
54
+ @displayName
55
+
56
+ # The full address of the location.
57
+ #
58
+ attr_accessor :address
59
+ @address
60
+
61
+
62
+ end
63
+
64
+ end
@@ -0,0 +1,222 @@
1
+ require 'json'
2
+ module Glass
3
+ SUBSCRIPTION="subscription"
4
+
5
+ # A subscription to events on a collection.
6
+ #
7
+ class Subscription
8
+ class << self
9
+ # The type of resource. This is always mirror#subscription.
10
+ #
11
+ attr_accessor :kind
12
+
13
+ ##
14
+ # Subscribe to notifications for the current user.
15
+ #
16
+ # @param [Google::APIClient] client
17
+ # Authorized client instance.
18
+ # @param [String] collection
19
+ # Collection to subscribe to (supported values are "timeline" and "locations").
20
+ # @param [String] user_token
21
+ # Opaque token used by the Glassware to identify the user the notification
22
+ # pings are sent for (recommended).
23
+ # @param [String] verify_token
24
+ # Opaque token used by the Glassware to verify that the notification pings are
25
+ # sent by the API (optional).
26
+ # @param [String] callback_url
27
+ # URL receiving notification pings (must be HTTPS).
28
+ # @param [Array] operation
29
+ # List of operations to subscribe to. Valid values are "UPDATE", "INSERT" and
30
+ # "DELETE" or nil to subscribe to all.
31
+ # @return nil
32
+ def subscribe(mirror, collection, user_token, verify_token, callback_url, operation)
33
+ subscription = mirror.subscriptions.insert.request_schema.new({
34
+ 'collection' => collection,
35
+ 'userToken' => user_token,
36
+ 'verifyToken' => verify_token,
37
+ 'callbackUrl' => callback_url,
38
+ 'operation' => operation})
39
+ result = client.execute(
40
+ :api_method => mirror.subscriptions.insert,
41
+ :body_object => subscription)
42
+ if result.error?
43
+ puts "An error occurred: #{result.data['error']['message']}"
44
+ end
45
+ end
46
+
47
+ ##
48
+ # Delete a subscription to a collection.
49
+ #
50
+ # @param [Google::APIClient] client
51
+ # Authorized client instance.
52
+ # @param [String] collection
53
+ # Collection to unsubscribe from (supported values are "timeline" and
54
+ # "locations").
55
+ # @return nil
56
+ def unsubscribe_from_notifications(client, collection)
57
+ mirror = client.discovered_api('mirror', 'v1')
58
+ result = client.execute(
59
+ :api_method => mirror.subscriptions.delete,
60
+ :parameters => { 'id' => collection })
61
+ if result.error?
62
+ puts "An error occurred: #{result.data['error']['message']}"
63
+ end
64
+ end
65
+
66
+ end
67
+
68
+ @@kind = MIRROR+"#"+SUBSCRIPTION
69
+
70
+ ##
71
+ # The Mirror Api
72
+ #
73
+ attr_accessor :mirror
74
+ @mirror
75
+
76
+ # The ID of the subscription.
77
+ #
78
+ attr_accessor :id
79
+ @id
80
+
81
+ # The time at which this subscription was last modified, formatted according to RFC 3339.
82
+ #
83
+ attr_accessor :updated
84
+ @updated
85
+
86
+ # The collection to subscribe to. Allowed values are:
87
+ # timeline - Changes in the timeline including insertion, deletion, and updates.
88
+ # locations - Location updates.
89
+ attr_accessor :collection
90
+ @collection
91
+ TIMELINE="timeline"
92
+ LOCATIONS="locations"
93
+
94
+ # A list of operations that should be subscribed to.
95
+ # An empty list indicates that all operations on the collection should be subscribed to. Allowed values are:
96
+ # UPDATE - The item has been updated.
97
+ # INSERT - A new item has been inserted.
98
+ # DELETE - The item has been deleted.
99
+ attr_accessor :operation
100
+ @operation= []
101
+
102
+ # The URL where notifications should be delivered (must start with https://).
103
+ #
104
+ attr_accessor :callbackUrl
105
+ @callbackUrl
106
+
107
+ # A secret token sent to the subscriber in notifications
108
+ # so that it can verify that the notification was generated by Google.
109
+ #
110
+ attr_accessor :verifyToken
111
+ @verifyToken
112
+
113
+ # An opaque token sent to the subscriber in notifications so that it can determine the ID of the user.
114
+ #
115
+ attr_accessor :userToken
116
+ @userToken
117
+
118
+ # Container object for notifications.
119
+ # This is not populated in the Subscription resource.
120
+ #
121
+ attr_accessor :notification
122
+ @notification
123
+
124
+ class Notification
125
+ # The collection that generated the notification.
126
+ #
127
+ attr_accessor :collection
128
+ @collection
129
+
130
+ # The ID of the item that generated the notification.
131
+ #
132
+ attr_accessor :itemId
133
+ @itemId
134
+
135
+ # The type of operation that generated the notification.
136
+ #
137
+ attr_accessor :operation
138
+ @operation
139
+
140
+ # The secret verify token provided by the service when it subscribed for notifications.
141
+ #
142
+ attr_accessor :verifyToken
143
+ @verifyToken
144
+
145
+ # The user token provided by the service when it subscribed for notifications.
146
+ #
147
+ attr_accessor :userToken
148
+ @userToken
149
+
150
+ # A list of actions taken by the user that triggered the notification.
151
+ #
152
+ attr_accessor :userActions
153
+ @userActions=[]
154
+
155
+ class Action
156
+ # The type of action. The value of this can be:
157
+ # SHARE - the user shared an item.
158
+ # REPLY - the user replied to an item.
159
+ # REPLY_ALL - the user replied to all recipients of an item.
160
+ # CUSTOM - the user selected a custom menu item on the timeline item.
161
+ # DELETE - the user deleted the item.
162
+ # PIN - the user pinned the item.
163
+ # UNPIN - the user unpinned the item.
164
+ # In the future, additional types may be added. UserActions with unrecognized types should be ignored.
165
+ #
166
+ attr_accessor :type
167
+ @type
168
+
169
+ SHARE="SHARE"
170
+ REPLY="REPLY"
171
+ REPLY_ALL="REPLY_ALL"
172
+ CUSTOM="CUSTOM"
173
+ DELETE="DELETE"
174
+ PIN="PIN"
175
+ UNPIN="UNPIN"
176
+
177
+ # An optional payload for the action.
178
+ #
179
+ # For actions of type CUSTOM, this is the ID of the custom menu item that was selected.
180
+ #
181
+ attr_accessor :payload
182
+ @payload
183
+
184
+ end
185
+
186
+ ##
187
+ # Subscribe to notifications for the current user.
188
+ #
189
+ # @param [Google::APIClient] Mirror
190
+ # Authorized client instance.
191
+ #
192
+ # @return nil
193
+ def insert(mirror=@mirror)
194
+ subscription = mirror.subscriptions.insert.request_schema.new(to_json)
195
+ result = client.execute(
196
+ :api_method => mirror.subscriptions.insert,
197
+ :body_object => subscription)
198
+ if result.error?
199
+ puts "An error occurred: #{result.data['error']['message']}"
200
+ end
201
+ end
202
+
203
+ ##
204
+ # Delete a subscription to a collection.
205
+ #
206
+ # @param [Google::APIClient] client
207
+ # Authorized client instance.
208
+ # @return nil
209
+ def delete(mirror=@mirror)
210
+ result = client.execute(
211
+ :api_method => mirror.subscriptions.delete,
212
+ :parameters => { 'id' => collection })
213
+ if result.error?
214
+ puts "An error occurred: #{result.data['error']['message']}"
215
+ end
216
+ end
217
+
218
+ end
219
+
220
+ end
221
+
222
+ end
@@ -0,0 +1,400 @@
1
+ require 'json'
2
+
3
+ module Glass
4
+ TIMELINE_ITEM="timelineItem"
5
+
6
+ # Each item in the user's timeline is represented as a TimelineItem JSON structure, described below.
7
+ #
8
+ class TimelineItem
9
+
10
+ def initialize(client=nil)
11
+ @client = client
12
+ end
13
+
14
+ class << self
15
+
16
+ # The type of resource. This is always mirror#timelineItem.
17
+ #
18
+ attr_reader :kind
19
+
20
+
21
+ def get(client, id)
22
+ client.exexute(
23
+ :api_method => client.timeline.get,
24
+ :parameters => {:id => id}
25
+ )
26
+ end
27
+
28
+ DISPLAY_TIME="displayTime"
29
+ WRITE_TIME="writeTime"
30
+
31
+ # Retrieves a list of timeline items for the authenticated user.
32
+ # @param [string] bundleId
33
+ # If true, tombstone records for deleted items will be returned.
34
+ # @param [boolean] includeDeleted
35
+ # If true, tombstone records for deleted items will be returned.
36
+ # @param [integer] maxResults
37
+ # The maximum number of items to include in the response, used for paging.
38
+ # @param [string] orderBy
39
+ # Controls the order in which timeline items are returned.
40
+ # Acceptable values are:
41
+ # "displayTime": Results will be ordered by displayTime (default). This is the same ordering as is used in the timeline on the device.
42
+ # "writeTime": Results will be ordered by the time at which they were last written to the data store.
43
+ # @param [string] pageToken
44
+ # Token for the page of results to return.
45
+ # @param [boolean] pinnedOnly
46
+ # If true, only pinned items will be returned.
47
+ # @param [string] sourceItemId
48
+ # If provided, only items with the given sourceItemId will be returned.
49
+ #
50
+ def list(client, params={})
51
+ result=[]
52
+ parameters = params
53
+ api_result = client.execute(
54
+ :api_method => mirror.timeline.list,
55
+ :parameters => parameters)
56
+ if api_result.success?
57
+ data = api_result.data
58
+ unless data.items.empty?
59
+ result << data.items
60
+ parameters[:pageToken]= data.next_page_token
61
+ result << list(client, parameters)
62
+ end
63
+ else
64
+ puts "An error occurred: #{result.data['error']['message']}"
65
+ end
66
+ result
67
+ end
68
+
69
+ end
70
+
71
+ ##
72
+ # The Client
73
+ #
74
+ attr_reader :client
75
+ @client
76
+
77
+ # The ID of the timeline item. This is unique within a user's timeline.
78
+ #
79
+ attr_accessor :id
80
+
81
+ # A URL that can be used to retrieve this item.
82
+ #
83
+ attr_accessor :selfLink
84
+
85
+ # The time at which this item was created, formatted according to RFC 3339.
86
+ #
87
+ attr_reader :created
88
+
89
+ # The time at which this item was last modified, formatted according to RFC 3339.
90
+ #
91
+ attr_accessor :updated
92
+
93
+ # The time that should be displayed when this item is viewed in the timeline,
94
+ # formatted according to RFC 3339. This user's timeline is sorted chronologically on display time,
95
+ # so this will also determine where the item is displayed in the timeline.
96
+ # If not set by the service, the display time defaults to the updated time.
97
+ #
98
+ attr_reader :displayTime
99
+
100
+ # When true, indicates this item is deleted, and only the ID property is set.
101
+ #
102
+ attr_accessor :isDeleted
103
+
104
+ # ETag for this item.
105
+ #
106
+ attr_reader :etag
107
+
108
+ # The user or group that created this item.
109
+ #
110
+ attr_accessor :creator
111
+
112
+ # If this item was generated as a reply to another item,
113
+ # this field will be set to the ID of the item being replied to.
114
+ # This can be used to attach a reply to the appropriate conversation or post.
115
+ #
116
+ attr_accessor :inReplyTo
117
+
118
+ # Text content of this item.
119
+ #
120
+ attr_accessor :text
121
+
122
+ # The speakable version of the content of this item.
123
+ # Along with the READ_ALOUD menu item,
124
+ # use this field to provide text that would be clearer when read aloud,
125
+ # or to provide extended information to what is displayed visually on Glass.
126
+ #
127
+ attr_accessor :speakableText
128
+
129
+ # A list of media attachments associated with this item.
130
+ #
131
+ attr_accessor :attachments
132
+
133
+ # The geographic location associated with this item.
134
+ #
135
+ attr_accessor :location
136
+
137
+ # A list of menu items that will be presented to the user when this item is selected in the timeline.
138
+ #
139
+ attr_accessor :menuItems
140
+
141
+ # Controls how notifications for this item are presented on the device.
142
+ # If this is missing, no notification will be generated.
143
+ #
144
+ attr_accessor :notification
145
+
146
+
147
+ # When true, indicates this item is pinned,
148
+ # which means it's grouped alongside "active" items like navigation and hangouts,
149
+ # on the opposite side of the home screen from historical (non-pinned) timeline items.
150
+ #
151
+ attr_accessor :isPinned
152
+
153
+ # The title of this item.
154
+ #
155
+ attr_accessor :title
156
+
157
+ # HTML content for this item. If both text and html are provided for an item,
158
+ # the html will be rendered in the timeline.
159
+ #
160
+ attr_accessor :html
161
+
162
+ # The bundle ID for this item. Services can specify a bundleId to group many items together.
163
+ # They appear under a single top-level item on the device.
164
+ #
165
+ attr_accessor :bundleId
166
+
167
+ # Additional pages of HTML content associated with this item.
168
+ # If this field is specified, the item will be displayed as a bundle,
169
+ # with the html field as the cover. It is an error to specify this field without specifying the html field.
170
+ #
171
+ attr_accessor :htmlPages
172
+
173
+ # Opaque string you can use to map a timeline item to data in your own service.
174
+ #
175
+ attr_accessor :sourceItemId
176
+
177
+ # A canonical URL pointing to the canonical/high quality version of the data represented by the timeline item.
178
+ #
179
+ attr_accessor :canonicalUrl
180
+
181
+ # Whether this item is a bundle cover.
182
+ #
183
+ # If an item is marked as a bundle cover,
184
+ # it will be the entry point to the bundle of items that have the same bundleId as that item.
185
+ # It will be shown only on the main timeline — not within the opened bundle.
186
+ #
187
+ # On the main timeline, items that are shown are:
188
+ # Items that have isBundleCover set to true
189
+ # Items that do not have a bundleId
190
+ #
191
+ # Items that do not have a bundleId
192
+ # In a bundle sub-timeline, items that are shown are:
193
+ # Items that have the bundleId in question AND isBundleCover set to false
194
+
195
+ attr_accessor :isBundleCover
196
+
197
+ # For pinned items, this determines the order in which the item is displayed in the timeline,
198
+ # with a higher score appearing closer to the clock. Note: setting this field is currently not supported.
199
+ #
200
+ attr_accessor :pinScore
201
+
202
+ # A list of contacts or groups that this item has been shared with.
203
+ #
204
+ attr_accessor :recipients
205
+
206
+ @@kind= MIRROR+"#"+TIMELINE_ITEM
207
+ @id
208
+ @selfLink
209
+ @created
210
+ @updated
211
+ @displayTime
212
+ @isDeleted
213
+ @etag
214
+ @creator #Todo User and Group
215
+ @inReplyTo
216
+ @text
217
+ @speakableText
218
+ @attachments=[]
219
+ @location
220
+ @menuItems=[]
221
+ @notification
222
+ @isPinned
223
+ @title
224
+ @html
225
+ @bundleId
226
+ @htmlPages=[]
227
+ @sourceItemId
228
+ @canonicalUrl
229
+ @isBundleCover
230
+ @pinScore
231
+ @recipients=[]
232
+
233
+ # Represents media content, such as a photo, that can be attached to a timeline item.
234
+ #
235
+ class Attachment
236
+
237
+ # The ID of the attachment.
238
+ #
239
+ attr_reader :id
240
+ @id
241
+
242
+ # The MIME type of the attachment.
243
+ # (supported content types are 'image/*', 'video/*' and 'audio/*').
244
+ #
245
+ attr_accessor :contentType
246
+ @contentType
247
+
248
+ TYPE_IMAGE='image/*'
249
+ TYPE_VIDEO='video/*'
250
+ TYPE_AUDIO='audio/*'
251
+
252
+ # The URL for the content.
253
+ #
254
+ attr_accessor :contentUrl
255
+ @contentUrl
256
+
257
+ # Indicates that the contentUrl is not available because the attachment content is still being processed.
258
+ # If the caller wishes to retrieve the content, it should try again later.
259
+ #
260
+ attr_reader :isProcessingContent
261
+ @isProcessingContent
262
+ end
263
+
264
+ class Notification
265
+
266
+ # Describes how important the notification is. Allowed values are:
267
+ # DEFAULT - Notifications of default importance. A chime will be played to alert contacts.
268
+ attr_accessor :level
269
+
270
+ # The time at which the notification should be delivered.
271
+ #
272
+ attr_accessor :deliveryTime
273
+
274
+ @level
275
+ @deliveryTime
276
+ end
277
+
278
+ class MenuItems
279
+
280
+ # The ID for this menu item. This is generated by the application and is treated as an opaque token.
281
+ #
282
+ attr_accessor :id
283
+
284
+ # Controls the behavior when the user picks the menu option. Allowed values are:
285
+ # CUSTOM - Custom action set by the service. When the user selects this menuItem,
286
+ # the API triggers a notification to your callbackUrl with the userActions.type set to CUSTOM and the userActions.payload set to the ID of this menu item.
287
+ # This is the default value.
288
+ # Built-in actions:
289
+ # REPLY - Initiate a reply to the timeline item using the voice recording UI. The creator attribute must be set in the timeline item for this menu to be available.
290
+ # REPLY_ALL - Same behavior as REPLY. The original timeline item's recipients will be added to the reply item.
291
+ # DELETE - Delete the timeline item.
292
+ # SHARE - Share the timeline item with the available contacts.
293
+ # READ_ALOUD - Read the timeline item's speakableText aloud; if this field is not set, read the text field; if none of those fields are set, this menu item is ignored.
294
+ # VOICE_CALL - Initiate a phone call using the timeline item's creator.phone_number attribute as recipient.
295
+ # NAVIGATE - Navigate to the timeline item's location.
296
+ # TOGGLE_PINNED - Toggle the isPinned state of the timeline item.
297
+ #
298
+ attr_accessor :actions
299
+
300
+ # For CUSTOM items, a list of values controlling the appearance of the menu item in each of its states.
301
+ # A value for the DEFAULT state must be provided.
302
+ # If the PENDING or CONFIRMED states are missing, they will not be shown.
303
+ #
304
+ attr_accessor :values
305
+
306
+ # If set to true on a CUSTOM menu item, that item will be removed from the menu after it is selected.
307
+ #
308
+ attr_accessor :removeWhenSelected
309
+
310
+ @id
311
+ @actions
312
+ @values=[]
313
+ @removeWhenSelected
314
+
315
+ REPLY="REPLY"
316
+ REPLY_ALL="REPLY_ALL"
317
+ DELETE="DELETE"
318
+ SHARE="SHARE"
319
+ READ_ALOUD="READ_ALOUD"
320
+ VOICE_CALL="VOICE_CALL"
321
+ NAVIGATE="NAVIGATE"
322
+ TOGGLE_PINNED="TOOGLE_PINNED"
323
+ CUSTOM="CUSTOM"
324
+
325
+ class Values
326
+
327
+ # The name to display for the menu item.
328
+ #
329
+ attr_accessor :displayName
330
+
331
+ # URL of an icon to display with the menu item.
332
+ #
333
+ attr_accessor :iconURL
334
+
335
+ # The state that this value applies to. Allowed values are:
336
+ # DEFAULT - Default value shown when displayed in the menuItems list.
337
+ # PENDING - Value shown when the menuItem has been selected by the user but can still be cancelled.
338
+ # CONFIRMED - Value shown when the menuItem has been selected by the user and can no longer be cancelled.
339
+ attr_accessor :state
340
+
341
+ @displayName
342
+ @iconURL
343
+ @state
344
+
345
+ DEFAULT="DEFAULT"
346
+ PENDING="PENDING"
347
+ CONFIRMED="CONFIRMED"
348
+ end
349
+ end
350
+
351
+
352
+ ##
353
+ # Insert a new Timeline Item in the user's glass.
354
+ #
355
+ # @param [Google::APIClient::API] client
356
+ # Authorized client instance.
357
+ # @return Array[Google::APIClient::Schema::Mirror::V1::TimelineItem]
358
+ # Timeline item instance if successful, nil otherwise.
359
+ def insert!(mirror=@client)
360
+ timeline_item = self
361
+ result = []
362
+ if file_upload?
363
+ for file in file_to_upload
364
+ media = Google::APIClient::UploadIO.new(file.contentUrl, file.content_type)
365
+ result << client.execute!(
366
+ :api_method => mirror.timeline.insert,
367
+ :body_object => timeline_item,
368
+ :media => media,
369
+ :parameters => {
370
+ :uploadType => 'multipart',
371
+ :alt => 'json'})
372
+ end
373
+ else
374
+ result << client.execute(
375
+ :api_method => mirror.timeline.insert,
376
+ :body_object => timeline_item)
377
+ end
378
+ return result.data
379
+ end
380
+
381
+ def file_upload?
382
+ flag=false
383
+ @attachments.each { |attachment|
384
+ flag = true unless attachment.id
385
+ }
386
+ return flag
387
+ end
388
+
389
+ def file_to_upload
390
+ file_to_upload=[]
391
+ @attachments.each { |attachment|
392
+ file_to_upload << attachment unless attachment.id
393
+ }
394
+ file_to_upload
395
+ end
396
+
397
+
398
+ end
399
+
400
+ end
@@ -1,3 +1,3 @@
1
1
  module Glass
2
- VERSION = "0.0.1.1"
2
+ VERSION = "0.0.1.2"
3
3
  end
@@ -0,0 +1,4 @@
1
+ sudo gem uninstall glass
2
+ gem build glass.gemspec
3
+ sudo gem install glass-0.0.1.2.gem
4
+ irb -r 'glass'
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: glass
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.1.1
4
+ version: 0.0.1.2
5
5
  prerelease:
6
6
  platform: ruby
7
7
  authors:
@@ -9,7 +9,7 @@ authors:
9
9
  autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
- date: 2013-04-16 00:00:00.000000000 Z
12
+ date: 2013-05-29 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: bundler
@@ -59,6 +59,54 @@ dependencies:
59
59
  - - ! '>='
60
60
  - !ruby/object:Gem::Version
61
61
  version: '0'
62
+ - !ruby/object:Gem::Dependency
63
+ name: redis
64
+ requirement: !ruby/object:Gem::Requirement
65
+ none: false
66
+ requirements:
67
+ - - ! '>='
68
+ - !ruby/object:Gem::Version
69
+ version: '0'
70
+ type: :runtime
71
+ prerelease: false
72
+ version_requirements: !ruby/object:Gem::Requirement
73
+ none: false
74
+ requirements:
75
+ - - ! '>='
76
+ - !ruby/object:Gem::Version
77
+ version: '0'
78
+ - !ruby/object:Gem::Dependency
79
+ name: redis-namespace
80
+ requirement: !ruby/object:Gem::Requirement
81
+ none: false
82
+ requirements:
83
+ - - ! '>='
84
+ - !ruby/object:Gem::Version
85
+ version: '0'
86
+ type: :runtime
87
+ prerelease: false
88
+ version_requirements: !ruby/object:Gem::Requirement
89
+ none: false
90
+ requirements:
91
+ - - ! '>='
92
+ - !ruby/object:Gem::Version
93
+ version: '0'
94
+ - !ruby/object:Gem::Dependency
95
+ name: httparty
96
+ requirement: !ruby/object:Gem::Requirement
97
+ none: false
98
+ requirements:
99
+ - - ! '>='
100
+ - !ruby/object:Gem::Version
101
+ version: '0'
102
+ type: :runtime
103
+ prerelease: false
104
+ version_requirements: !ruby/object:Gem::Requirement
105
+ none: false
106
+ requirements:
107
+ - - ! '>='
108
+ - !ruby/object:Gem::Version
109
+ version: '0'
62
110
  description: Glass Gem for Google Glass
63
111
  email:
64
112
  - dam.cavailles@laposte.net
@@ -73,7 +121,13 @@ files:
73
121
  - Rakefile
74
122
  - glass.gemspec
75
123
  - lib/glass.rb
124
+ - lib/glass/config.rb
125
+ - lib/glass/contacts/contact.rb
126
+ - lib/glass/locations/location.rb
127
+ - lib/glass/subscriptions/subscription.rb
128
+ - lib/glass/timeline/timeline_item.rb
76
129
  - lib/glass/version.rb
130
+ - rebuild.sh
77
131
  homepage: https://github.com/thedamfr/glass
78
132
  licenses:
79
133
  - MIT
@@ -95,7 +149,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
95
149
  version: '0'
96
150
  requirements: []
97
151
  rubyforge_project:
98
- rubygems_version: 1.8.23
152
+ rubygems_version: 1.8.25
99
153
  signing_key:
100
154
  specification_version: 3
101
155
  summary: This Gem is meant to allow you to quickly build a Google Glass Application.