nylas 0.15.5
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.
- checksums.yaml +7 -0
- data/LICENSE.txt +22 -0
- data/README.md +363 -0
- data/lib/account.rb +32 -0
- data/lib/api_thread.rb +56 -0
- data/lib/calendar.rb +15 -0
- data/lib/contact.rb +10 -0
- data/lib/draft.rb +32 -0
- data/lib/event.rb +34 -0
- data/lib/file.rb +31 -0
- data/lib/inbox.rb +141 -0
- data/lib/message.rb +41 -0
- data/lib/namespace.rb +147 -0
- data/lib/parameters.rb +26 -0
- data/lib/restful_model.rb +80 -0
- data/lib/restful_model_collection.rb +149 -0
- data/lib/rfc2882.rb +15 -0
- data/lib/tag.rb +9 -0
- data/lib/time_attr_accessor.rb +12 -0
- data/lib/version.rb +3 -0
- metadata +206 -0
data/lib/calendar.rb
ADDED
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
require 'restful_model'
|
|
2
|
+
require 'event'
|
|
3
|
+
|
|
4
|
+
module Inbox
|
|
5
|
+
class Calendar < RestfulModel
|
|
6
|
+
|
|
7
|
+
parameter :name
|
|
8
|
+
parameter :description
|
|
9
|
+
|
|
10
|
+
def events
|
|
11
|
+
@events ||= RestfulModelCollection.new(Event, @_api, @namespace, {:calendar_id=>@id})
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
end
|
|
15
|
+
end
|
data/lib/contact.rb
ADDED
data/lib/draft.rb
ADDED
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
require 'restful_model'
|
|
2
|
+
|
|
3
|
+
module Inbox
|
|
4
|
+
class Draft < Message
|
|
5
|
+
|
|
6
|
+
parameter :thread_id
|
|
7
|
+
parameter :version
|
|
8
|
+
parameter :reply_to_message_id
|
|
9
|
+
|
|
10
|
+
def attach(file)
|
|
11
|
+
file.save! unless file.id
|
|
12
|
+
@file_ids.push(file.id)
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
def send!
|
|
16
|
+
url = @_api.url_for_path("/n/#{@namespace_id}/send")
|
|
17
|
+
if @id
|
|
18
|
+
data = {:draft_id => @id, :version => @version}
|
|
19
|
+
else
|
|
20
|
+
data = as_json()
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
::RestClient.post(url, data.to_json, :content_type => :json) do |response, request, result|
|
|
24
|
+
json = Inbox.interpret_response(result, response, :expected_class => Object)
|
|
25
|
+
self.inflate(json)
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
self
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
end
|
|
32
|
+
end
|
data/lib/event.rb
ADDED
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
require 'restful_model'
|
|
2
|
+
|
|
3
|
+
module Inbox
|
|
4
|
+
class Event < RestfulModel
|
|
5
|
+
|
|
6
|
+
parameter :title
|
|
7
|
+
parameter :description
|
|
8
|
+
parameter :location
|
|
9
|
+
parameter :read_only
|
|
10
|
+
parameter :participants
|
|
11
|
+
parameter :when
|
|
12
|
+
parameter :calendar_id
|
|
13
|
+
parameter :namespace_id
|
|
14
|
+
parameter :recurrence
|
|
15
|
+
parameter :cancelled
|
|
16
|
+
parameter :master_event_id
|
|
17
|
+
parameter :original_start_time
|
|
18
|
+
|
|
19
|
+
def as_json(options = {})
|
|
20
|
+
hash = super(options)
|
|
21
|
+
|
|
22
|
+
# Delete nil values from the hash
|
|
23
|
+
hash.delete_if { |key, value| value.nil? }
|
|
24
|
+
|
|
25
|
+
# The API doesn't like to receive: "object": "timespan" in the when block.
|
|
26
|
+
if hash.has_key?('when') and hash['when'].has_key?('object')
|
|
27
|
+
hash['when'].delete('object')
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
return hash
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
end
|
|
34
|
+
end
|
data/lib/file.rb
ADDED
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
require 'restful_model'
|
|
2
|
+
|
|
3
|
+
module Inbox
|
|
4
|
+
class File < RestfulModel
|
|
5
|
+
|
|
6
|
+
parameter :size
|
|
7
|
+
parameter :filename
|
|
8
|
+
parameter :content_type
|
|
9
|
+
parameter :is_embedded
|
|
10
|
+
parameter :message_id
|
|
11
|
+
|
|
12
|
+
# For uploading the file
|
|
13
|
+
parameter :file
|
|
14
|
+
|
|
15
|
+
def inflate(json)
|
|
16
|
+
super
|
|
17
|
+
content_type = json["content-type"] if json["content-type"]
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
def save!
|
|
21
|
+
::RestClient.post(url, {:file => @file}) do |response, request, result|
|
|
22
|
+
json = Inbox.interpret_response(result, response, :expected_class => Object)
|
|
23
|
+
json = json[0] if (json.class == Array)
|
|
24
|
+
inflate(json)
|
|
25
|
+
end
|
|
26
|
+
self
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
end
|
|
30
|
+
end
|
|
31
|
+
|
data/lib/inbox.rb
ADDED
|
@@ -0,0 +1,141 @@
|
|
|
1
|
+
require 'version'
|
|
2
|
+
require 'rest-client'
|
|
3
|
+
require 'restful_model_collection'
|
|
4
|
+
require 'json'
|
|
5
|
+
require 'namespace'
|
|
6
|
+
require 'account'
|
|
7
|
+
|
|
8
|
+
module Inbox
|
|
9
|
+
|
|
10
|
+
class AccessDenied < StandardError; end
|
|
11
|
+
class ResourceNotFound < StandardError; end
|
|
12
|
+
class NoAuthToken < StandardError; end
|
|
13
|
+
class UnexpectedAccountAction < StandardError; end
|
|
14
|
+
class UnexpectedResponse < StandardError; end
|
|
15
|
+
class APIError < StandardError
|
|
16
|
+
attr_accessor :error_type
|
|
17
|
+
def initialize(type, error)
|
|
18
|
+
super(error)
|
|
19
|
+
self.error_type = type
|
|
20
|
+
end
|
|
21
|
+
end
|
|
22
|
+
class InvalidRequest < APIError; end
|
|
23
|
+
class MessageRejected < APIError; end
|
|
24
|
+
class SendingQuotaExceeded < APIError; end
|
|
25
|
+
class ServiceUnavailable < APIError; end
|
|
26
|
+
|
|
27
|
+
def self.interpret_http_status(result)
|
|
28
|
+
# Handle HTTP errors and RestClient errors
|
|
29
|
+
raise ResourceNotFound.new if result.code.to_i == 404
|
|
30
|
+
raise AccessDenied.new if result.code.to_i == 403
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
def self.interpret_response(result, result_content, options = {})
|
|
34
|
+
# Handle HTTP errors
|
|
35
|
+
Inbox.interpret_http_status(result)
|
|
36
|
+
|
|
37
|
+
# Handle content expectation errors
|
|
38
|
+
raise UnexpectedResponse.new if options[:expected_class] && result_content.empty?
|
|
39
|
+
json = JSON.parse(result_content)
|
|
40
|
+
if json.is_a?(Hash) && (json['type'] == 'api_error' or json['type'] == 'invalid_request_error')
|
|
41
|
+
if result.code.to_i == 400
|
|
42
|
+
exc = InvalidRequest
|
|
43
|
+
elsif result.code.to_i == 402
|
|
44
|
+
exc = MessageRejected
|
|
45
|
+
elsif result.code.to_i == 429
|
|
46
|
+
exc = SendingQuotaExceeded
|
|
47
|
+
elsif result.code.to_i == 503
|
|
48
|
+
exc = ServiceUnavailable
|
|
49
|
+
else
|
|
50
|
+
exc = APIError
|
|
51
|
+
end
|
|
52
|
+
raise exc.new(json['type'], json['message'])
|
|
53
|
+
end
|
|
54
|
+
raise UnexpectedResponse.new(result.msg) if result.is_a?(Net::HTTPClientError)
|
|
55
|
+
raise UnexpectedResponse.new if options[:expected_class] && !json.is_a?(options[:expected_class])
|
|
56
|
+
json
|
|
57
|
+
|
|
58
|
+
rescue JSON::ParserError => e
|
|
59
|
+
# Handle parsing errors
|
|
60
|
+
raise UnexpectedResponse.new(e.message)
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
class API
|
|
65
|
+
attr_accessor :api_server
|
|
66
|
+
attr_reader :access_token
|
|
67
|
+
attr_reader :app_id
|
|
68
|
+
attr_reader :app_secret
|
|
69
|
+
|
|
70
|
+
def initialize(app_id, app_secret, access_token = nil, api_server = 'https://api.nylas.com',
|
|
71
|
+
service_domain = 'api.nylas.com')
|
|
72
|
+
raise "When overriding the Inbox API server address, you must include https://" unless api_server.include?('://')
|
|
73
|
+
@api_server = api_server
|
|
74
|
+
@access_token = access_token
|
|
75
|
+
@app_secret = app_secret
|
|
76
|
+
@app_id = app_id
|
|
77
|
+
@service_domain = service_domain
|
|
78
|
+
@version = Inbox::VERSION
|
|
79
|
+
|
|
80
|
+
if ::RestClient.before_execution_procs.empty?
|
|
81
|
+
::RestClient.add_before_execution_proc do |req, params|
|
|
82
|
+
req.add_field('X-Inbox-API-Wrapper', 'ruby')
|
|
83
|
+
req['User-Agent'] = "Ruby SDK #{@version} - #{RUBY_VERSION}"
|
|
84
|
+
end
|
|
85
|
+
end
|
|
86
|
+
end
|
|
87
|
+
|
|
88
|
+
def url_for_path(path)
|
|
89
|
+
raise NoAuthToken.new if @access_token == nil and (@app_secret != nil or @app_id != nil)
|
|
90
|
+
protocol, domain = @api_server.split('//')
|
|
91
|
+
"#{protocol}//#{@access_token}:@#{domain}#{path}"
|
|
92
|
+
end
|
|
93
|
+
|
|
94
|
+
def url_for_authentication(redirect_uri, login_hint = '', options = {})
|
|
95
|
+
trialString = 'false'
|
|
96
|
+
if options[:trial] == true
|
|
97
|
+
trialString = 'true'
|
|
98
|
+
end
|
|
99
|
+
"https://#{@service_domain}/oauth/authorize?client_id=#{@app_id}&trial=#{trialString}&response_type=code&scope=email&login_hint=#{login_hint}&redirect_uri=#{redirect_uri}"
|
|
100
|
+
end
|
|
101
|
+
|
|
102
|
+
def url_for_management
|
|
103
|
+
protocol, domain = @api_server.split('//')
|
|
104
|
+
accounts_path = "#{protocol}//#{@app_secret}:@#{domain}/a/#{@app_id}/accounts"
|
|
105
|
+
end
|
|
106
|
+
|
|
107
|
+
def set_access_token(token)
|
|
108
|
+
@access_token = token
|
|
109
|
+
end
|
|
110
|
+
|
|
111
|
+
def token_for_code(code)
|
|
112
|
+
data = {
|
|
113
|
+
'client_id' => app_id,
|
|
114
|
+
'client_secret' => app_secret,
|
|
115
|
+
'grant_type' => 'authorization_code',
|
|
116
|
+
'code' => code
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
::RestClient.post("https://#{@service_domain}/oauth/token", data) do |response, request, result|
|
|
120
|
+
json = Inbox.interpret_response(result, response, :expected_class => Object)
|
|
121
|
+
return json['access_token']
|
|
122
|
+
end
|
|
123
|
+
end
|
|
124
|
+
|
|
125
|
+
# Convenience Methods
|
|
126
|
+
|
|
127
|
+
def namespaces
|
|
128
|
+
@namespaces ||= RestfulModelCollection.new(Namespace, self, nil)
|
|
129
|
+
@namespaces
|
|
130
|
+
end
|
|
131
|
+
|
|
132
|
+
# Billing Methods
|
|
133
|
+
|
|
134
|
+
def accounts
|
|
135
|
+
@accounts ||= ManagementModelCollection.new(Account, self, nil)
|
|
136
|
+
@accounts
|
|
137
|
+
end
|
|
138
|
+
end
|
|
139
|
+
end
|
|
140
|
+
|
|
141
|
+
Nylas = Inbox.clone
|
data/lib/message.rb
ADDED
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
require 'restful_model'
|
|
2
|
+
require 'file'
|
|
3
|
+
require 'rfc2882'
|
|
4
|
+
|
|
5
|
+
module Inbox
|
|
6
|
+
class Message < RestfulModel
|
|
7
|
+
|
|
8
|
+
parameter :subject
|
|
9
|
+
parameter :snippet
|
|
10
|
+
parameter :from
|
|
11
|
+
parameter :to
|
|
12
|
+
parameter :cc
|
|
13
|
+
parameter :bcc
|
|
14
|
+
parameter :date
|
|
15
|
+
parameter :thread_id
|
|
16
|
+
parameter :body
|
|
17
|
+
parameter :unread
|
|
18
|
+
|
|
19
|
+
def inflate(json)
|
|
20
|
+
super
|
|
21
|
+
@to ||= []
|
|
22
|
+
@cc ||= []
|
|
23
|
+
@bcc ||= []
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
def files
|
|
27
|
+
@files ||= RestfulModelCollection.new(File, @_api, @namespace_id, {:message_id=>@id})
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
def raw
|
|
31
|
+
model = nil
|
|
32
|
+
collection = RestfulModelCollection.new(Message, @_api, @namespace_id, {:message_id=>@id})
|
|
33
|
+
RestClient.get("#{collection.url}/#{id}/rfc2822"){ |response,request,result|
|
|
34
|
+
json = Inbox.interpret_response(result, response, {:expected_class => Object})
|
|
35
|
+
model = Rfc2822.new(@_api)
|
|
36
|
+
model.inflate(json)
|
|
37
|
+
}
|
|
38
|
+
model
|
|
39
|
+
end
|
|
40
|
+
end
|
|
41
|
+
end
|
data/lib/namespace.rb
ADDED
|
@@ -0,0 +1,147 @@
|
|
|
1
|
+
require 'restful_model'
|
|
2
|
+
require 'account'
|
|
3
|
+
require 'tag'
|
|
4
|
+
require 'message'
|
|
5
|
+
require 'draft'
|
|
6
|
+
require 'contact'
|
|
7
|
+
require 'file'
|
|
8
|
+
require 'calendar'
|
|
9
|
+
require 'event'
|
|
10
|
+
|
|
11
|
+
# Rather than saying require 'thread', we need to explicitly force
|
|
12
|
+
# the thread model to load. Otherwise, we can't reference it below.
|
|
13
|
+
# Thread still refers to the built-in Thread type, and Inbox::Thread
|
|
14
|
+
# is undefined.
|
|
15
|
+
load "api_thread.rb"
|
|
16
|
+
|
|
17
|
+
module Inbox
|
|
18
|
+
|
|
19
|
+
class Namespace < RestfulModel
|
|
20
|
+
|
|
21
|
+
parameter :account_id
|
|
22
|
+
parameter :name
|
|
23
|
+
parameter :email_address
|
|
24
|
+
parameter :provider
|
|
25
|
+
|
|
26
|
+
def self.collection_name
|
|
27
|
+
"n"
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
def threads
|
|
31
|
+
@threads ||= RestfulModelCollection.new(Thread, @_api, @id)
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
def tags
|
|
35
|
+
@tags ||= RestfulModelCollection.new(Tag, @_api, @id)
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
def messages
|
|
39
|
+
@messages ||= RestfulModelCollection.new(Message, @_api, @id)
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
def files
|
|
43
|
+
@files ||= RestfulModelCollection.new(File, @_api, @id)
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
def drafts
|
|
47
|
+
@drafts ||= RestfulModelCollection.new(Draft, @_api, @id)
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
def contacts
|
|
51
|
+
@contacts ||= RestfulModelCollection.new(Contact, @_api, @id)
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
def calendars
|
|
55
|
+
@calendars ||= RestfulModelCollection.new(Calendar, @_api, @id)
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
def events
|
|
59
|
+
@events ||= RestfulModelCollection.new(Event, @_api, @id)
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
def get_cursor(timestamp)
|
|
63
|
+
# Get the cursor corresponding to a specific timestamp.
|
|
64
|
+
path = @_api.url_for_path("/n/#{@namespace_id}/delta/generate_cursor")
|
|
65
|
+
data = { :start => timestamp }
|
|
66
|
+
|
|
67
|
+
cursor = nil
|
|
68
|
+
|
|
69
|
+
RestClient.post(path, data.to_json, :content_type => :json) do |response,request,result|
|
|
70
|
+
json = Inbox.interpret_response(result, response, {:expected_class => Object})
|
|
71
|
+
cursor = json["cursor"]
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
cursor
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
OBJECTS_TABLE = {
|
|
78
|
+
"account" => Inbox::Account,
|
|
79
|
+
"calendar" => Inbox::Calendar,
|
|
80
|
+
"draft" => Inbox::Draft,
|
|
81
|
+
"thread" => Inbox::Thread,
|
|
82
|
+
"contact" => Inbox::Contact,
|
|
83
|
+
"event" => Inbox::Event,
|
|
84
|
+
"file" => Inbox::File,
|
|
85
|
+
"message" => Inbox::Message,
|
|
86
|
+
"namespace" => Inbox::Namespace,
|
|
87
|
+
"tag" => Inbox::Tag,
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
def deltas(cursor, exclude_types=[])
|
|
91
|
+
raise 'Please provide a block for receiving the delta objects' if !block_given?
|
|
92
|
+
exclude_string = ""
|
|
93
|
+
|
|
94
|
+
if exclude_types.any?
|
|
95
|
+
exclude_string = "&exclude_types="
|
|
96
|
+
|
|
97
|
+
exclude_types.each do |value|
|
|
98
|
+
count = 0
|
|
99
|
+
if OBJECTS_TABLE.has_value?(value)
|
|
100
|
+
param_name = OBJECTS_TABLE.key(value)
|
|
101
|
+
exclude_string += "#{param_name},"
|
|
102
|
+
end
|
|
103
|
+
end
|
|
104
|
+
end
|
|
105
|
+
|
|
106
|
+
exclude_string = exclude_string[0..-2]
|
|
107
|
+
|
|
108
|
+
# loop and yield deltas until we've come to the end.
|
|
109
|
+
loop do
|
|
110
|
+
path = @_api.url_for_path("/n/#{@namespace_id}/delta?cursor=#{cursor}#{exclude_string}")
|
|
111
|
+
json = nil
|
|
112
|
+
|
|
113
|
+
RestClient.get(path) do |response,request,result|
|
|
114
|
+
json = Inbox.interpret_response(result, response, {:expected_class => Object})
|
|
115
|
+
end
|
|
116
|
+
|
|
117
|
+
start_cursor = json["cursor_start"]
|
|
118
|
+
end_cursor = json["cursor_end"]
|
|
119
|
+
|
|
120
|
+
json["deltas"].each do |delta|
|
|
121
|
+
object = delta['object']
|
|
122
|
+
if object == 'message'
|
|
123
|
+
# Drafts are messages underneath
|
|
124
|
+
object = delta['attributes']['object']
|
|
125
|
+
end
|
|
126
|
+
cls = OBJECTS_TABLE[object]
|
|
127
|
+
obj = cls.new(@_api, @namespace_id)
|
|
128
|
+
|
|
129
|
+
case delta["event"]
|
|
130
|
+
when 'create', 'modify'
|
|
131
|
+
obj.inflate(delta['attributes'])
|
|
132
|
+
obj.cursor = delta["cursor"]
|
|
133
|
+
yield delta["event"], obj
|
|
134
|
+
when 'delete'
|
|
135
|
+
obj.id = delta["id"]
|
|
136
|
+
obj.cursor = delta["cursor"]
|
|
137
|
+
yield delta["event"], obj
|
|
138
|
+
end
|
|
139
|
+
end
|
|
140
|
+
|
|
141
|
+
break if start_cursor == end_cursor
|
|
142
|
+
cursor = end_cursor
|
|
143
|
+
end
|
|
144
|
+
end
|
|
145
|
+
|
|
146
|
+
end
|
|
147
|
+
end
|