glass 0.0.1.1 → 0.0.1.2

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/.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.