contextio-lite 0.0.2
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.gitignore +14 -0
- data/Gemfile +4 -0
- data/LICENSE.txt +22 -0
- data/README.md +114 -0
- data/Rakefile +2 -0
- data/contextio-lite.gemspec +27 -0
- data/lib/contextio.rb +14 -0
- data/lib/contextio/api/abstract_api.rb +213 -0
- data/lib/contextio/api/association_helpers.rb +17 -0
- data/lib/contextio/api/resource.rb +248 -0
- data/lib/contextio/api/resource_collection.rb +193 -0
- data/lib/contextio/lite.rb +43 -0
- data/lib/contextio/lite/api.rb +30 -0
- data/lib/contextio/lite/email_account.rb +48 -0
- data/lib/contextio/lite/email_account_collection.rb +44 -0
- data/lib/contextio/lite/folder.rb +18 -0
- data/lib/contextio/lite/folder_collection.rb +17 -0
- data/lib/contextio/lite/message.rb +77 -0
- data/lib/contextio/lite/message_collection.rb +15 -0
- data/lib/contextio/lite/url_builder.rb +95 -0
- data/lib/contextio/lite/user.rb +61 -0
- data/lib/contextio/lite/user_collection.rb +40 -0
- data/lib/contextio/lite/webhook.rb +43 -0
- data/lib/contextio/lite/webhook_collection.rb +28 -0
- metadata +139 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: 9a4c95db7436871ec9123eb36072dd02e24f5078
|
4
|
+
data.tar.gz: 55b65431a9f3f5b517992e5ed3cde2ac2aa4198a
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 56066f305c8e1341b3f911f18a25bce82297c99c1b6697569704d954fae1503f9c57179e16a8cc54917d4924b6c0d1b2e133da7738b1786e71fd137989d42641
|
7
|
+
data.tar.gz: 4d245707e67caeac959882263e47dd5b121f7464bf01251b707259e4b463d8c2992bc402f6c776af395da4d1d6a690b17c14256a6910464ed6c6853e495b86ab
|
data/.gitignore
ADDED
data/Gemfile
ADDED
data/LICENSE.txt
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
Copyright (c) 2015 javi
|
2
|
+
|
3
|
+
MIT License
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
6
|
+
a copy of this software and associated documentation files (the
|
7
|
+
"Software"), to deal in the Software without restriction, including
|
8
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
9
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
10
|
+
permit persons to whom the Software is furnished to do so, subject to
|
11
|
+
the following conditions:
|
12
|
+
|
13
|
+
The above copyright notice and this permission notice shall be
|
14
|
+
included in all copies or substantial portions of the Software.
|
15
|
+
|
16
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
17
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
18
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
19
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
20
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
21
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
22
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,114 @@
|
|
1
|
+
# Contextio::Lite
|
2
|
+
|
3
|
+
This is API client for the [Context.IO Lite](https://context.io/) version based on [Context.IO Ruby](https://github.com/contextio/contextio-ruby) for the 2.0 version.
|
4
|
+
It works in the same way as the official, calling first a user object and then working with the collections...
|
5
|
+
|
6
|
+
## Installation
|
7
|
+
|
8
|
+
Add this line to your application's Gemfile:
|
9
|
+
|
10
|
+
```ruby
|
11
|
+
gem 'contextio-lite'
|
12
|
+
```
|
13
|
+
|
14
|
+
And then execute:
|
15
|
+
|
16
|
+
$ bundle
|
17
|
+
|
18
|
+
Or install it yourself as:
|
19
|
+
|
20
|
+
$ gem install contextio-lite
|
21
|
+
|
22
|
+
## Usage
|
23
|
+
|
24
|
+
### Get a client
|
25
|
+
|
26
|
+
```ruby
|
27
|
+
require 'contextio/lite'
|
28
|
+
|
29
|
+
client = ContextIO::Lite.new(API_KEY, API_SECRET)
|
30
|
+
```
|
31
|
+
|
32
|
+
### Dealing with users
|
33
|
+
|
34
|
+
List all the registered lite users IDs
|
35
|
+
|
36
|
+
```ruby
|
37
|
+
p client.users.map(&:id)
|
38
|
+
```
|
39
|
+
|
40
|
+
CRUD
|
41
|
+
|
42
|
+
```ruby
|
43
|
+
user = client.users.create email: 'bob@gmail.com', first_name: 'Bob', server: 'imap.gmail.com' username: 'bob', 'use_sll':true, 'port': 993, 'type': 'IMAP
|
44
|
+
|
45
|
+
user_id = user.id
|
46
|
+
|
47
|
+
client.users[user_id].update :hash_attrs
|
48
|
+
|
49
|
+
client.users[user_id].delete
|
50
|
+
```
|
51
|
+
|
52
|
+
### Retrieving messages
|
53
|
+
|
54
|
+
To access the emails, first you need to select an email_account and the folder containing your email
|
55
|
+
|
56
|
+
So if you want to list the user email accounts or folders you can do like this
|
57
|
+
|
58
|
+
```ruby
|
59
|
+
p client[user_id].email_accounts.map(&:label)
|
60
|
+
p client[user_id].email_accounts[label].folders.map(&:name) # can access email_accounts by number => email_accounts[0]
|
61
|
+
```
|
62
|
+
|
63
|
+
Listing all the email subjects inside a folder (limited by context IO to 100)
|
64
|
+
```ruby
|
65
|
+
client[user_id].email_accounts[0].folders['INBOX'].messages do
|
66
|
+
p messages.subject
|
67
|
+
end
|
68
|
+
```
|
69
|
+
|
70
|
+
And if you want to filter the emails
|
71
|
+
|
72
|
+
```ruby
|
73
|
+
client[user_id].email_accounts[0].folder['INBOX'].messages.where(limit: 3)
|
74
|
+
```
|
75
|
+
|
76
|
+
You also may want to access the content of one message
|
77
|
+
|
78
|
+
```ruby
|
79
|
+
client[user_id].email_accounts[0].folder['INBOX'].messages['<message_id>'].body_plain # or body_html
|
80
|
+
|
81
|
+
client[user_id].email_accounts[0].folder['INBOX'].messages['<message_id>'].with(include_body:true).body_plain
|
82
|
+
```
|
83
|
+
|
84
|
+
The first one calls https://api.context.io/lite/users/id/email_accounts/label/folders/folder/messages/message_id/body
|
85
|
+
and the second calls https://api.context.io/lite/users/id/email_accounts/label/folders/folder/messages/message_id?include_body=1
|
86
|
+
but they return the same.
|
87
|
+
|
88
|
+
And the above should works also with 'flags' or 'headers'.
|
89
|
+
|
90
|
+
### Webhooks
|
91
|
+
|
92
|
+
One of the main feature that this version has and 2.0 doesn't is the real-time webhooks.
|
93
|
+
So in order to work with them we can do
|
94
|
+
|
95
|
+
```ruby
|
96
|
+
client[user_id].webhooks.map(&:webhook_id) # listing all the webhooks an user has
|
97
|
+
|
98
|
+
client[user_id].create callback_url, failure_url, filter_folder_added: 'INBOX', include_body: true
|
99
|
+
```
|
100
|
+
|
101
|
+
So it will call the callback_url every time there is a new message on the user INBOX folder, posting the message info and body included.
|
102
|
+
|
103
|
+
|
104
|
+
## Contributing
|
105
|
+
|
106
|
+
1. Fork it ( https://github.com/javijuol/contextio-lite/fork )
|
107
|
+
2. Create your feature branch (`git checkout -b my-new-feature`)
|
108
|
+
3. Commit your changes (`git commit -am 'Add some feature'`)
|
109
|
+
4. Push to the branch (`git push origin my-new-feature`)
|
110
|
+
5. Create a new Pull Request
|
111
|
+
|
112
|
+
## Copyright
|
113
|
+
|
114
|
+
This gem is distributed under the MIT License. See LICENSE.md for details.
|
data/Rakefile
ADDED
@@ -0,0 +1,27 @@
|
|
1
|
+
# coding: utf-8
|
2
|
+
lib = File.expand_path('../lib', __FILE__)
|
3
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
4
|
+
require File.expand_path('../lib/contextio', __FILE__)
|
5
|
+
|
6
|
+
Gem::Specification.new do |spec|
|
7
|
+
spec.name = 'contextio-lite'
|
8
|
+
spec.version = ContextIO.version
|
9
|
+
spec.authors = ['Javier Juan']
|
10
|
+
spec.email = ['javier@promivia.com']
|
11
|
+
spec.summary = %q{Provides interface to Context.IO Lite API}
|
12
|
+
spec.description = %q{Short implementation of a client rest API for the Context.IO Lite API}
|
13
|
+
spec.homepage = 'https://github.com/javijuol/contextio-lite'
|
14
|
+
spec.license = 'MIT'
|
15
|
+
|
16
|
+
spec.files = `git ls-files -z`.split("\x0")
|
17
|
+
spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
|
18
|
+
spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
|
19
|
+
spec.require_paths = ['lib']
|
20
|
+
|
21
|
+
spec.add_development_dependency 'bundler', '~> 1.7'
|
22
|
+
spec.add_development_dependency 'rake', '~> 10.0'
|
23
|
+
spec.add_development_dependency 'debase', '~> 0'
|
24
|
+
spec.add_development_dependency 'faraday', '~> 0.9.1'
|
25
|
+
spec.add_development_dependency 'faraday_middleware', '~> 0.9.1'
|
26
|
+
|
27
|
+
end
|
data/lib/contextio.rb
ADDED
@@ -0,0 +1,213 @@
|
|
1
|
+
require 'uri'
|
2
|
+
require 'json'
|
3
|
+
require 'faraday'
|
4
|
+
require 'faraday_middleware'
|
5
|
+
|
6
|
+
module ContextIO
|
7
|
+
module API
|
8
|
+
class AbstractAPI
|
9
|
+
|
10
|
+
# @private
|
11
|
+
BASE_URL = 'https://api.context.io'
|
12
|
+
|
13
|
+
# @return [String] The version of the Context.IO API this version of the
|
14
|
+
# gem is intended for use with.
|
15
|
+
def self.version
|
16
|
+
raise NotDefinedError, 'VERSION is not defined in your API subclassed model.' if self::VERSION.nil?
|
17
|
+
self::VERSION
|
18
|
+
end
|
19
|
+
|
20
|
+
# @return [String] The base URL the API is served from.
|
21
|
+
def self.base_url
|
22
|
+
BASE_URL
|
23
|
+
end
|
24
|
+
|
25
|
+
def self.user_agent_string
|
26
|
+
raise NotDefinedError, 'user_agent_string undefined in your API subclassed model.'
|
27
|
+
end
|
28
|
+
|
29
|
+
def user_agent_string
|
30
|
+
self.class.user_agent_string
|
31
|
+
end
|
32
|
+
|
33
|
+
attr_accessor :base_url, :version
|
34
|
+
|
35
|
+
# @!attribute [r] key
|
36
|
+
# @return [String] The OAuth key for the user's Context.IO account.
|
37
|
+
# @!attribute [r] secret
|
38
|
+
# @return [String] The OAuth secret for the user's Context.IO account.
|
39
|
+
# @!attribute [r] opts
|
40
|
+
# @return [Hash] opts Optional options for OAuth connections.
|
41
|
+
attr_reader :key, :secret, :opts
|
42
|
+
|
43
|
+
# @param [String] key The user's OAuth key for their Context.IO account.
|
44
|
+
# @param [String] secret The user's OAuth secret for their Context.IO account.
|
45
|
+
# @param [Hash] opts Optional options for OAuth connections. ie. :timeout and :open_timeout are supported
|
46
|
+
def initialize(key, secret, opts={})
|
47
|
+
@key = key
|
48
|
+
@secret = secret
|
49
|
+
@opts = opts || {}
|
50
|
+
@base_url = self.class.base_url
|
51
|
+
@version = self.class.version
|
52
|
+
end
|
53
|
+
|
54
|
+
# Generates the path for a resource_path and params hash for use with the API.
|
55
|
+
#
|
56
|
+
# @param [String] resource_path The resource_path or full resource URL for
|
57
|
+
# the resource being acted on.
|
58
|
+
# @param [{String, Symbol => String, Symbol, Array<String, Symbol>}] params
|
59
|
+
# A Hash of the query parameters for the action represented by this path.
|
60
|
+
def path(resource_path, params = {})
|
61
|
+
"/#{version}/#{strip_resource_path(resource_path)}#{self.class.hash_to_url_params(params)}"
|
62
|
+
end
|
63
|
+
|
64
|
+
# Makes a request against the Context.IO API.
|
65
|
+
#
|
66
|
+
# @param [String, Symbol] method The HTTP verb for the request (lower case).
|
67
|
+
# @param [String] resource_path The path to the resource in question.
|
68
|
+
# @param [{String, Symbol => String, Symbol, Array<String, Symbol>}] params
|
69
|
+
# A Hash of the query parameters for the action represented by this
|
70
|
+
# request.
|
71
|
+
#
|
72
|
+
# @raise [API::Error] if the response code isn't in the 200 or 300 range.
|
73
|
+
def request(method, resource_path, params = {})
|
74
|
+
response = oauth_request(method, resource_path, params, { 'Accept' => 'application/json' })
|
75
|
+
|
76
|
+
with_error_handling(response) do |response|
|
77
|
+
parse_json(response.body)
|
78
|
+
end
|
79
|
+
end
|
80
|
+
|
81
|
+
def raw_request(method, resource_path, params={})
|
82
|
+
response = oauth_request(method, resource_path, params)
|
83
|
+
|
84
|
+
with_error_handling(response) do |response|
|
85
|
+
response.body
|
86
|
+
end
|
87
|
+
end
|
88
|
+
|
89
|
+
protected
|
90
|
+
|
91
|
+
# Makes a request signed for OAuth, encoding parameters correctly, etc.
|
92
|
+
#
|
93
|
+
# @param [String, Symbol] method The HTTP verb for the request (lower case).
|
94
|
+
# @param [String] resource_path The path to the resource in question.
|
95
|
+
# @param [{String, Symbol => String, Symbol, Array<String, Symbol>}] params
|
96
|
+
# A Hash of the query parameters for the action represented by this
|
97
|
+
# request.
|
98
|
+
# @param [{String, Symbol => String, Symbol, Array<String, Symbol>}] headers
|
99
|
+
# A Hash of headers to be merged with the default headers for making
|
100
|
+
# requests.
|
101
|
+
#
|
102
|
+
# @return [Faraday::Response] The response object from the request.
|
103
|
+
def oauth_request(method, resource_path, params, headers=nil)
|
104
|
+
normalized_params = params.inject({}) do |normalized_params, (key, value)|
|
105
|
+
normalized_params[key.to_sym] = value
|
106
|
+
normalized_params
|
107
|
+
end
|
108
|
+
|
109
|
+
connection.send(method, path(resource_path), normalized_params, headers) do |request|
|
110
|
+
if request.method == :put
|
111
|
+
request.params = normalized_params
|
112
|
+
request.body = {}
|
113
|
+
end
|
114
|
+
end
|
115
|
+
end
|
116
|
+
|
117
|
+
# So that we can accept full URLs, this strips the domain and version number
|
118
|
+
# out and returns just the resource path.
|
119
|
+
#
|
120
|
+
# @param [#to_s] resource_path The full URL or path for a resource.
|
121
|
+
#
|
122
|
+
# @return [String] The resource path.
|
123
|
+
def strip_resource_path(resource_path)
|
124
|
+
resource_path.to_s.gsub("#{base_url}/#{version}/", '')
|
125
|
+
end
|
126
|
+
|
127
|
+
# Context.IO's API expects query parameters that are arrays to be comma
|
128
|
+
# separated, rather than submitted more than once. This munges those arrays
|
129
|
+
# and then URL-encodes the whole thing into a query string.
|
130
|
+
#
|
131
|
+
# @param [{String, Symbol => String, Symbol, Array<String, Symbol>}] params
|
132
|
+
# A Hash of the query parameters.
|
133
|
+
#
|
134
|
+
# @return [String] A URL-encoded version of the query parameters.
|
135
|
+
def self.hash_to_url_params(params = {})
|
136
|
+
return '' if params.empty?
|
137
|
+
|
138
|
+
params = params.inject({}) do |memo, (k, v)|
|
139
|
+
memo[k] = Array(v).join(',')
|
140
|
+
|
141
|
+
memo
|
142
|
+
end
|
143
|
+
|
144
|
+
"?#{URI.encode_www_form(params)}"
|
145
|
+
end
|
146
|
+
|
147
|
+
# @!attribute [r] connection
|
148
|
+
# @return [Faraday::Connection] A handle on the Faraday connection object.
|
149
|
+
def connection
|
150
|
+
@connection ||= Faraday::Connection.new(base_url) do |faraday|
|
151
|
+
faraday.headers['User-Agent'] = user_agent_string
|
152
|
+
|
153
|
+
faraday.request :oauth, consumer_key: key, consumer_secret: secret
|
154
|
+
faraday.request :url_encoded
|
155
|
+
|
156
|
+
faraday.adapter Faraday.default_adapter
|
157
|
+
end
|
158
|
+
end
|
159
|
+
|
160
|
+
# Errors can come in a few shapes and we want to detect them and extract the
|
161
|
+
# useful information. If no errors are found, it calls the provided block
|
162
|
+
# and passes the response through.
|
163
|
+
#
|
164
|
+
# @param [Faraday::Response] response A response object from making a request to the
|
165
|
+
# API with Faraday.
|
166
|
+
#
|
167
|
+
# @raise [API::Error] if the response code isn't in the 200 or 300 range.
|
168
|
+
def with_error_handling(response, &block)
|
169
|
+
return block.call(response) if response.success?
|
170
|
+
|
171
|
+
parsed_body = parse_json(response.body)
|
172
|
+
message = determine_best_error_message(parsed_body) || "HTTP #{response.status} Error"
|
173
|
+
|
174
|
+
raise ContextIO::API::Error, message
|
175
|
+
end
|
176
|
+
|
177
|
+
# Parses JSON if there's valid JSON passed in.
|
178
|
+
#
|
179
|
+
# @param [String] document A string you suspect may be a JSON document.
|
180
|
+
#
|
181
|
+
# @return [Hash, Array, Nil] Either a parsed version of the JSON document or
|
182
|
+
# nil, if the document wasn't valid JSON.
|
183
|
+
def parse_json(document)
|
184
|
+
return JSON.parse(document.to_s)
|
185
|
+
rescue JSON::ParserError => e
|
186
|
+
return nil
|
187
|
+
end
|
188
|
+
|
189
|
+
|
190
|
+
# Given a parsed JSON body from an error response, figures out if it can
|
191
|
+
# pull useful information therefrom.
|
192
|
+
#
|
193
|
+
# @param [Hash] parsed_body A Hash parsed from a JSON document that may
|
194
|
+
# describe an error condition.
|
195
|
+
#
|
196
|
+
# @return [String, Nil] If it can, it will return a human-readable
|
197
|
+
# error-describing String. Otherwise, nil.
|
198
|
+
def determine_best_error_message(parsed_body)
|
199
|
+
return unless parsed_body.respond_to?(:[])
|
200
|
+
|
201
|
+
if parsed_body['type'] == 'error'
|
202
|
+
return parsed_body['value']
|
203
|
+
elsif parsed_body.has_key?('success') && !parsed_body['success']
|
204
|
+
return [parsed_body['feedback_code'], parsed_body['connectionLog']].compact.join("\n")
|
205
|
+
end
|
206
|
+
end
|
207
|
+
end
|
208
|
+
|
209
|
+
class NotDefinedError < StandardError; end
|
210
|
+
class Error < StandardError; end
|
211
|
+
|
212
|
+
end
|
213
|
+
end
|
@@ -0,0 +1,17 @@
|
|
1
|
+
module ContextIO
|
2
|
+
module API
|
3
|
+
module AssociationHelpers
|
4
|
+
def self.class_for_association_name(association_name)
|
5
|
+
associations[association_name]
|
6
|
+
end
|
7
|
+
|
8
|
+
def self.register_resource(klass, association_name)
|
9
|
+
associations[association_name] = klass
|
10
|
+
end
|
11
|
+
|
12
|
+
def self.associations
|
13
|
+
@associations ||= {}
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|