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 +0 -0
- data/Gemfile +0 -0
- data/LICENSE.txt +0 -0
- data/README.md +105 -1
- data/Rakefile +0 -0
- data/glass.gemspec +3 -0
- data/lib/glass.rb +287 -9
- data/lib/glass/config.rb +65 -0
- data/lib/glass/contacts/contact.rb +66 -0
- data/lib/glass/locations/location.rb +64 -0
- data/lib/glass/subscriptions/subscription.rb +222 -0
- data/lib/glass/timeline/timeline_item.rb +400 -0
- data/lib/glass/version.rb +1 -1
- data/rebuild.sh +4 -0
- metadata +57 -3
data/.gitignore
CHANGED
File without changes
|
data/Gemfile
CHANGED
File without changes
|
data/LICENSE.txt
CHANGED
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
|
-
|
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
|
data/glass.gemspec
CHANGED
@@ -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
|
data/lib/glass.rb
CHANGED
@@ -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'
|
data/lib/glass/config.rb
ADDED
@@ -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
|
data/lib/glass/version.rb
CHANGED
data/rebuild.sh
ADDED
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.
|
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-
|
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.
|
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.
|