openemail 0.0.1
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/README.md +183 -0
- data/lib/openemail/api_paths.rb +249 -0
- data/lib/openemail/client.rb +69 -0
- data/lib/openemail/constants.rb +851 -0
- data/lib/openemail/credentials.rb +53 -0
- data/lib/openemail/decode.rb +34 -0
- data/lib/openemail/defaults.rb +111 -0
- data/lib/openemail/endpoint.rb +144 -0
- data/lib/openemail/errors.rb +121 -0
- data/lib/openemail/http.rb +172 -0
- data/lib/openemail/languages.rb +42 -0
- data/lib/openemail/pagination.rb +63 -0
- data/lib/openemail/protocol.rb +169 -0
- data/lib/openemail/raw_body.rb +83 -0
- data/lib/openemail/request_path.rb +50 -0
- data/lib/openemail/resource.rb +70 -0
- data/lib/openemail/resources/account.rb +64 -0
- data/lib/openemail/resources/addresses.rb +38 -0
- data/lib/openemail/resources/analytics.rb +17 -0
- data/lib/openemail/resources/app_host.rb +23 -0
- data/lib/openemail/resources/audiences.rb +90 -0
- data/lib/openemail/resources/billing.rb +64 -0
- data/lib/openemail/resources/branding.rb +24 -0
- data/lib/openemail/resources/broadcasts.rb +65 -0
- data/lib/openemail/resources/calendar.rb +59 -0
- data/lib/openemail/resources/chats.rb +31 -0
- data/lib/openemail/resources/contacts.rb +111 -0
- data/lib/openemail/resources/dns_connections.rb +19 -0
- data/lib/openemail/resources/domains.rb +153 -0
- data/lib/openemail/resources/drafts.rb +35 -0
- data/lib/openemail/resources/emails.rb +100 -0
- data/lib/openemail/resources/encryption.rb +19 -0
- data/lib/openemail/resources/exports.rb +27 -0
- data/lib/openemail/resources/files.rb +95 -0
- data/lib/openemail/resources/forms.rb +111 -0
- data/lib/openemail/resources/imports.rb +86 -0
- data/lib/openemail/resources/keys.rb +127 -0
- data/lib/openemail/resources/labels.rb +39 -0
- data/lib/openemail/resources/languages.rb +11 -0
- data/lib/openemail/resources/me.rb +19 -0
- data/lib/openemail/resources/members.rb +74 -0
- data/lib/openemail/resources/provider_imports.rb +40 -0
- data/lib/openemail/resources/roles.rb +39 -0
- data/lib/openemail/resources/rules.rb +57 -0
- data/lib/openemail/resources/security.rb +19 -0
- data/lib/openemail/resources/senders.rb +15 -0
- data/lib/openemail/resources/settings.rb +16 -0
- data/lib/openemail/resources/subscriptions.rb +30 -0
- data/lib/openemail/resources/support.rb +11 -0
- data/lib/openemail/resources/suppressions.rb +31 -0
- data/lib/openemail/resources/temp_mail.rb +67 -0
- data/lib/openemail/resources/templates.rb +151 -0
- data/lib/openemail/resources/threads.rb +104 -0
- data/lib/openemail/resources/tools.rb +19 -0
- data/lib/openemail/resources/tracking.rb +63 -0
- data/lib/openemail/resources/webhooks.rb +142 -0
- data/lib/openemail/resources/workspaces.rb +28 -0
- data/lib/openemail/results.rb +33 -0
- data/lib/openemail/transport.rb +272 -0
- data/lib/openemail/update_notice.rb +69 -0
- data/lib/openemail/version.rb +5 -0
- data/lib/openemail/webhook.rb +82 -0
- data/lib/openemail/wire.rb +95 -0
- data/lib/openemail.rb +125 -0
- metadata +109 -0
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module OpenEmail
|
|
4
|
+
module Pagination
|
|
5
|
+
CURSOR = Protocol::CURSOR_STYLES[:CURSOR]
|
|
6
|
+
PAGE_TOKEN = Protocol::CURSOR_STYLES[:PAGE_TOKEN]
|
|
7
|
+
|
|
8
|
+
module_function
|
|
9
|
+
|
|
10
|
+
def fetch_page(transport, path, query: nil, style: CURSOR, limit: nil, cursor: nil, api_key: nil)
|
|
11
|
+
payload = transport.request(path, query: page_query(query, style, limit, cursor), api_key:)
|
|
12
|
+
envelope = payload.is_a?(Hash) ? payload : {}
|
|
13
|
+
next_cursor = cursor_of(envelope[style[:FIELD].to_sym])
|
|
14
|
+
|
|
15
|
+
Page.new(items: items_of(envelope), has_more: more?(envelope, next_cursor), next_cursor:)
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
def iterate(transport, path, query: nil, style: CURSOR, limit: nil, cursor: nil, api_key: nil)
|
|
19
|
+
walk(cursor) { |current| fetch_page(transport, path, query:, style:, limit:, cursor: current, api_key:) }
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
def collect_all(transport, path, **options)
|
|
23
|
+
iterate(transport, path, **options).to_a
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
def fetch_list(transport, path, query: nil, api_key: nil)
|
|
27
|
+
payload = transport.request(path, query:, api_key:)
|
|
28
|
+
items_of(payload.is_a?(Hash) ? payload : {})
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
def walk(cursor, &fetch)
|
|
32
|
+
Enumerator.new do |yielder|
|
|
33
|
+
current = cursor
|
|
34
|
+
|
|
35
|
+
loop do
|
|
36
|
+
page = fetch.call(current)
|
|
37
|
+
page.items.each { |item| yielder << item }
|
|
38
|
+
|
|
39
|
+
break if !page.has_more || page.next_cursor.nil? || page.next_cursor == current
|
|
40
|
+
|
|
41
|
+
current = page.next_cursor
|
|
42
|
+
end
|
|
43
|
+
end
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
def page_query(query, style, limit, cursor)
|
|
47
|
+
(query || {}).merge(Protocol::QUERY_KEYS[:LIMIT] => limit, style[:PARAM] => cursor)
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
def items_of(envelope)
|
|
51
|
+
envelope[:data].is_a?(Array) ? envelope[:data] : []
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
def more?(envelope, next_cursor)
|
|
55
|
+
flag = envelope[:hasMore]
|
|
56
|
+
(flag == true || flag == false) ? flag : !next_cursor.nil?
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
def cursor_of(value)
|
|
60
|
+
(value.is_a?(String) && !value.empty?) ? value : nil
|
|
61
|
+
end
|
|
62
|
+
end
|
|
63
|
+
end
|
|
@@ -0,0 +1,169 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module OpenEmail
|
|
4
|
+
module Protocol
|
|
5
|
+
ACCESS_TOKEN_RULES = {RESERVED_PREFIX: "oe_", MAX_LENGTH: 512}.freeze
|
|
6
|
+
|
|
7
|
+
API_KEY_MODES = {LIVE: "live", TEST: "test"}.freeze
|
|
8
|
+
|
|
9
|
+
API_KEY_PREFIXES = {LIVE: "oe_live_", TEST: "oe_test_"}.freeze
|
|
10
|
+
|
|
11
|
+
CONTENT_TYPES = {
|
|
12
|
+
JSON: "application/json",
|
|
13
|
+
CALENDAR: "text/calendar",
|
|
14
|
+
OCTET_STREAM: "application/octet-stream"
|
|
15
|
+
}.freeze
|
|
16
|
+
|
|
17
|
+
CURSOR_STYLES = {
|
|
18
|
+
CURSOR: {PARAM: "cursor", FIELD: "nextCursor"}.freeze,
|
|
19
|
+
PAGE_TOKEN: {PARAM: "pageToken", FIELD: "nextPageToken"}.freeze
|
|
20
|
+
}.freeze
|
|
21
|
+
|
|
22
|
+
ENV_VARS = {
|
|
23
|
+
API_KEY: "OPENEMAIL_API_KEY",
|
|
24
|
+
ACCESS_TOKEN: "OPENEMAIL_ACCESS_TOKEN",
|
|
25
|
+
BASE_URL: "OPENEMAIL_BASE_URL"
|
|
26
|
+
}.freeze
|
|
27
|
+
|
|
28
|
+
ERROR_CODES = {
|
|
29
|
+
INSUFFICIENT_SCOPE: "insufficient_scope",
|
|
30
|
+
UNRECOGNISED_RESPONSE: "unrecognised_response"
|
|
31
|
+
}.freeze
|
|
32
|
+
|
|
33
|
+
ERROR_TYPE_BY_STATUS = {
|
|
34
|
+
401 => "authentication_error",
|
|
35
|
+
403 => "permission_error",
|
|
36
|
+
404 => "not_found_error",
|
|
37
|
+
409 => "conflict_error",
|
|
38
|
+
422 => "validation_error",
|
|
39
|
+
429 => "rate_limit_error"
|
|
40
|
+
}.freeze
|
|
41
|
+
|
|
42
|
+
HEADER_KEYS = {
|
|
43
|
+
AUTHORIZATION: "Authorization",
|
|
44
|
+
ACCEPT: "Accept",
|
|
45
|
+
CONTENT_TYPE: "Content-Type",
|
|
46
|
+
USER_AGENT: "User-Agent",
|
|
47
|
+
IDEMPOTENCY_KEY: "Idempotency-Key",
|
|
48
|
+
RETRY_AFTER: "retry-after",
|
|
49
|
+
REQUEST_ID: "x-request-id"
|
|
50
|
+
}.freeze
|
|
51
|
+
|
|
52
|
+
HTTP_METHODS = {GET: "GET", POST: "POST", PUT: "PUT", PATCH: "PATCH", DELETE: "DELETE"}.freeze
|
|
53
|
+
|
|
54
|
+
IDEMPOTENCY_KEY_PREFIX = "oe-"
|
|
55
|
+
|
|
56
|
+
LANGUAGE_ALIASES = [
|
|
57
|
+
["zh", "zh-Hans"].freeze,
|
|
58
|
+
["zh-cn", "zh-Hans"].freeze,
|
|
59
|
+
["zh-sg", "zh-Hans"].freeze,
|
|
60
|
+
["cmn", "zh-Hans"].freeze,
|
|
61
|
+
["zh-tw", "zh-Hant"].freeze,
|
|
62
|
+
["zh-mo", "zh-HK"].freeze,
|
|
63
|
+
["iw", "he"].freeze,
|
|
64
|
+
["in", "id"].freeze,
|
|
65
|
+
["ji", "yi"].freeze,
|
|
66
|
+
["mo", "ro"].freeze,
|
|
67
|
+
["sh", "sr-Latn"].freeze,
|
|
68
|
+
["sr-latn-rs", "sr-Latn"].freeze,
|
|
69
|
+
["prs", "fa-AF"].freeze,
|
|
70
|
+
["fa-af", "fa-AF"].freeze,
|
|
71
|
+
["kmr", "ku"].freeze,
|
|
72
|
+
["pt-pt", "pt"].freeze,
|
|
73
|
+
["es-mx", "es-419"].freeze,
|
|
74
|
+
["es-ar", "es-419"].freeze,
|
|
75
|
+
["es-co", "es-419"].freeze,
|
|
76
|
+
["es-cl", "es-419"].freeze
|
|
77
|
+
].freeze
|
|
78
|
+
|
|
79
|
+
LIST_SEPARATOR = ","
|
|
80
|
+
|
|
81
|
+
LOCAL_HOSTS = ["localhost", "::1", "[::1]", "127.0.0.1", "0.0.0.0", "::", "[::]"].freeze
|
|
82
|
+
|
|
83
|
+
LOOPBACK_HOSTNAMES = ["localhost", "::1", "[::1]"].freeze
|
|
84
|
+
|
|
85
|
+
LOOPBACK_IPV4_ADDRESS = "127.0.0.1"
|
|
86
|
+
|
|
87
|
+
LOOPBACK_IPV6_ADDRESS = "[::1]"
|
|
88
|
+
|
|
89
|
+
QUERY_KEYS = {
|
|
90
|
+
LIMIT: "limit",
|
|
91
|
+
CURSOR: "cursor",
|
|
92
|
+
STATUS: "status",
|
|
93
|
+
FROM: "from",
|
|
94
|
+
TO: "to",
|
|
95
|
+
TIMEZONE: "timezone",
|
|
96
|
+
ENABLED: "enabled",
|
|
97
|
+
RULE_ID: "ruleId",
|
|
98
|
+
THREAD_ID: "threadId",
|
|
99
|
+
FOLDER: "folder",
|
|
100
|
+
QUERY: "query",
|
|
101
|
+
LABEL_IDS: "labelIds",
|
|
102
|
+
OPENED: "opened",
|
|
103
|
+
CLICKED: "clicked",
|
|
104
|
+
DAYS: "days",
|
|
105
|
+
MINUTES: "minutes",
|
|
106
|
+
GRAIN: "grain",
|
|
107
|
+
OFFSET_MINUTES: "offsetMinutes",
|
|
108
|
+
INCLUDE_MACHINE: "includeMachine",
|
|
109
|
+
REASSIGN_TO: "reassignTo",
|
|
110
|
+
SOURCE: "source",
|
|
111
|
+
ADDRESS: "address",
|
|
112
|
+
DIRECTION: "direction",
|
|
113
|
+
FILTER: "filter",
|
|
114
|
+
SEARCH: "search",
|
|
115
|
+
SORT: "sort",
|
|
116
|
+
PAGE: "page",
|
|
117
|
+
PAGE_SIZE: "pageSize",
|
|
118
|
+
VERSION: "version",
|
|
119
|
+
TRACKED: "tracked",
|
|
120
|
+
Q: "q",
|
|
121
|
+
AUDIENCE_IDS: "audienceIds",
|
|
122
|
+
AUDIENCE_ID: "audienceId",
|
|
123
|
+
BROADCAST_ID: "broadcastId",
|
|
124
|
+
BROADCAST_IDS: "broadcastIds",
|
|
125
|
+
EMAIL: "email",
|
|
126
|
+
BLOCKED: "blocked",
|
|
127
|
+
DATE_FROM: "dateFrom",
|
|
128
|
+
DATE_TO: "dateTo",
|
|
129
|
+
FROM_CONTACTS: "fromContacts",
|
|
130
|
+
REASON: "reason",
|
|
131
|
+
KIND: "kind",
|
|
132
|
+
ENDPOINT_IDS: "endpointIds",
|
|
133
|
+
KEY_IDS: "keyIds",
|
|
134
|
+
SINCE: "since",
|
|
135
|
+
UNTIL: "until",
|
|
136
|
+
FAILED_ONLY: "failedOnly",
|
|
137
|
+
FILENAME: "filename",
|
|
138
|
+
DOMAIN: "domain",
|
|
139
|
+
SCHEDULED_FROM: "scheduledFrom",
|
|
140
|
+
SCHEDULED_TO: "scheduledTo",
|
|
141
|
+
ADDRESSES: "addresses",
|
|
142
|
+
OFFSET: "offset",
|
|
143
|
+
REFRESH: "refresh",
|
|
144
|
+
CONFIRM: "confirm",
|
|
145
|
+
USERNAME: "username"
|
|
146
|
+
}.freeze
|
|
147
|
+
|
|
148
|
+
RETRY = {
|
|
149
|
+
STATUSES: [408, 429, 500, 502, 503, 504].freeze,
|
|
150
|
+
METHODS: ["GET"].freeze,
|
|
151
|
+
BASE_BACKOFF_MS: 500,
|
|
152
|
+
MAX_BACKOFF_MS: 8000,
|
|
153
|
+
MAX_HONOURED_RETRY_AFTER_MS: 60000,
|
|
154
|
+
RATE_LIMIT_STATUS: 429
|
|
155
|
+
}.freeze
|
|
156
|
+
|
|
157
|
+
SEALED_ENCRYPTION_FORMATS = ["pgp-mime", "pgp-inline", "smime-encrypted"].freeze
|
|
158
|
+
|
|
159
|
+
UNSPECIFIED_HOSTNAMES = ["0.0.0.0", "::", "[::]"].freeze
|
|
160
|
+
|
|
161
|
+
WEBHOOK_SIGNATURE = {
|
|
162
|
+
VERSION: "v1",
|
|
163
|
+
TIMESTAMP: "t",
|
|
164
|
+
PAIR_SEPARATOR: ",",
|
|
165
|
+
VALUE_SEPARATOR: "=",
|
|
166
|
+
TOLERANCE_SECONDS: 300
|
|
167
|
+
}.freeze
|
|
168
|
+
end
|
|
169
|
+
end
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module OpenEmail
|
|
4
|
+
module RawBody
|
|
5
|
+
module_function
|
|
6
|
+
|
|
7
|
+
def read(data)
|
|
8
|
+
case data
|
|
9
|
+
when String then data
|
|
10
|
+
when Pathname then File.binread(data)
|
|
11
|
+
else read_stream(data)
|
|
12
|
+
end
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
def size(data)
|
|
16
|
+
case data
|
|
17
|
+
when String then data.bytesize
|
|
18
|
+
when Pathname then File.size(data)
|
|
19
|
+
else stream_size(data)
|
|
20
|
+
end
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
def slice(data, offset, length)
|
|
24
|
+
case data
|
|
25
|
+
when String then data.byteslice(offset, length)
|
|
26
|
+
when Pathname then File.open(data, "rb") { |file| stream_slice(file, offset, length) }
|
|
27
|
+
else stream_slice(data, offset, length)
|
|
28
|
+
end
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
def content_type(data, explicit)
|
|
32
|
+
return explicit unless explicit.nil? || explicit.to_s.empty?
|
|
33
|
+
|
|
34
|
+
declared = data.respond_to?(:content_type) ? data.content_type : nil
|
|
35
|
+
return declared if declared.is_a?(String) && !declared.empty?
|
|
36
|
+
|
|
37
|
+
name = file_name(data)
|
|
38
|
+
Defaults::CONTENT_TYPES_BY_EXTENSION[File.extname(name).downcase] if name
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
def file_name(data)
|
|
42
|
+
return data.original_filename if data.respond_to?(:original_filename) && data.original_filename
|
|
43
|
+
return File.basename(data) if data.is_a?(Pathname)
|
|
44
|
+
|
|
45
|
+
File.basename(data.path) if data.respond_to?(:path) && data.path
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
def read_stream(data)
|
|
49
|
+
raise ArgumentError, Defaults::MESSAGES[:RAW_BODY_SHAPE] unless data.respond_to?(:read)
|
|
50
|
+
|
|
51
|
+
rewind(data)
|
|
52
|
+
data.read.to_s.b
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
def stream_size(data)
|
|
56
|
+
raise ArgumentError, Defaults::MESSAGES[:RAW_BODY_SHAPE] unless data.respond_to?(:read)
|
|
57
|
+
return data.size if data.respond_to?(:size)
|
|
58
|
+
|
|
59
|
+
data.stat.size
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
def stream_slice(data, offset, length)
|
|
63
|
+
raise ArgumentError, Defaults::MESSAGES[:RAW_BODY_SHAPE] unless data.respond_to?(:read)
|
|
64
|
+
|
|
65
|
+
position(data, offset)
|
|
66
|
+
data.read(length).to_s.b
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
def position(data, offset)
|
|
70
|
+
return data.seek(offset) if data.respond_to?(:seek)
|
|
71
|
+
|
|
72
|
+
rewind(data) if offset.zero?
|
|
73
|
+
rescue Errno::ESPIPE
|
|
74
|
+
nil
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
def rewind(data)
|
|
78
|
+
data.rewind if data.respond_to?(:rewind)
|
|
79
|
+
rescue Errno::ESPIPE
|
|
80
|
+
nil
|
|
81
|
+
end
|
|
82
|
+
end
|
|
83
|
+
end
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module OpenEmail
|
|
4
|
+
module RequestPath
|
|
5
|
+
module_function
|
|
6
|
+
|
|
7
|
+
def fill(pattern, params)
|
|
8
|
+
values = params.transform_keys(&:to_s)
|
|
9
|
+
|
|
10
|
+
pattern.scan(Defaults::PATH_PARAM) { |(name)| assert_segment(values[name]) }
|
|
11
|
+
|
|
12
|
+
pattern
|
|
13
|
+
.gsub(Defaults::OPTIONAL_PATH_PARAM) { optional_segment(values[Regexp.last_match(1)]) }
|
|
14
|
+
.gsub(Defaults::PATH_PARAM) { encode(values[Regexp.last_match(1)]) }
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
def assert_segment(value)
|
|
18
|
+
text = segment_text(value)
|
|
19
|
+
|
|
20
|
+
raise ArgumentError, Defaults::MESSAGES[:EMPTY_SEGMENT] if text.nil? || text.empty?
|
|
21
|
+
raise ArgumentError, "#{text.inspect} #{Defaults::MESSAGES[:DOT_SEGMENT]}" if Defaults::DOT_SEGMENT.match?(text)
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
def segment_text(value)
|
|
25
|
+
case value
|
|
26
|
+
when String, Symbol then utf8(value.to_s)
|
|
27
|
+
when Numeric then Wire.number(value).to_s
|
|
28
|
+
end
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
def utf8(text)
|
|
32
|
+
converted = (text.encoding == Encoding::BINARY) ? text.dup.force_encoding(Encoding::UTF_8) : text.encode(Encoding::UTF_8)
|
|
33
|
+
raise ArgumentError, Defaults::MESSAGES[:SEGMENT_ENCODING] unless converted.valid_encoding?
|
|
34
|
+
|
|
35
|
+
converted
|
|
36
|
+
rescue EncodingError
|
|
37
|
+
raise ArgumentError, Defaults::MESSAGES[:SEGMENT_ENCODING]
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
def optional_segment(value)
|
|
41
|
+
(value.nil? || value == "") ? "" : "/#{encode(value)}"
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
def encode(value)
|
|
45
|
+
segment_text(value).to_s.gsub(Defaults::URI_COMPONENT_UNSAFE) do |character|
|
|
46
|
+
character.bytes.map { |byte| format("%%%02X", byte) }.join
|
|
47
|
+
end
|
|
48
|
+
end
|
|
49
|
+
end
|
|
50
|
+
end
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module OpenEmail
|
|
4
|
+
class Resource
|
|
5
|
+
CURSOR = Pagination::CURSOR
|
|
6
|
+
PAGE_TOKEN = Pagination::PAGE_TOKEN
|
|
7
|
+
|
|
8
|
+
def initialize(transport)
|
|
9
|
+
@transport = transport
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
def inspect
|
|
13
|
+
"#<#{self.class.name}>"
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
private
|
|
17
|
+
|
|
18
|
+
def request(path, **options)
|
|
19
|
+
@transport.request(path, **options)
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
def fill(pattern, **params)
|
|
23
|
+
RequestPath.fill(pattern, params)
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
def payload(body, fields)
|
|
27
|
+
Wire.body(body, fields)
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
def query_params(**values)
|
|
31
|
+
values.to_h { |name, value| [Protocol::QUERY_KEYS.fetch(name.to_s.upcase.to_sym), value] }
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
def joined(values)
|
|
35
|
+
Wire.collection?(values) ? Wire.list(values).join(Protocol::LIST_SEPARATOR) : values
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
def flag(value)
|
|
39
|
+
value&.to_s
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
def instant(value)
|
|
43
|
+
Wire.instant(value)
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
def fetch_page(path, query = nil, style: CURSOR, limit: nil, cursor: nil, api_key: nil)
|
|
47
|
+
Pagination.fetch_page(@transport, path, query:, style:, limit:, cursor:, api_key:)
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
def iterate_pages(path, query = nil, style: CURSOR, limit: nil, cursor: nil, api_key: nil, &)
|
|
51
|
+
each_or_enumerator(Pagination.iterate(@transport, path, query:, style:, limit:, cursor:, api_key:), &)
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
def each_or_enumerator(enumerator, &block)
|
|
55
|
+
block ? enumerator.each(&block) : enumerator
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
def collect_all(path, query = nil, style: CURSOR, limit: nil, cursor: nil, api_key: nil)
|
|
59
|
+
iterate_pages(path, query, style:, limit:, cursor:, api_key:).to_a
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
def fetch_list(path, query = nil, api_key: nil)
|
|
63
|
+
Pagination.fetch_list(@transport, path, query:, api_key:)
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
def raw_content_type(data, content_type)
|
|
67
|
+
RawBody.content_type(data, content_type)
|
|
68
|
+
end
|
|
69
|
+
end
|
|
70
|
+
end
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module OpenEmail
|
|
4
|
+
module Resources
|
|
5
|
+
class Account < Resource
|
|
6
|
+
def get_notifications(api_key: nil)
|
|
7
|
+
request(ApiPaths::ACCOUNT_NOTIFICATIONS, api_key:)
|
|
8
|
+
end
|
|
9
|
+
|
|
10
|
+
def set_email_notification(category, body = nil, api_key: nil, **fields)
|
|
11
|
+
request(fill(ApiPaths::ACCOUNT_NOTIFICATION_EMAIL, category:), method: :put,
|
|
12
|
+
body: payload(body, fields).slice("enabled"), repeatable: true, api_key:)
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
def set_push_muted(workspace_id, body = nil, api_key: nil, **fields)
|
|
16
|
+
request(fill(ApiPaths::ACCOUNT_NOTIFICATION_PUSH, workspaceId: workspace_id), method: :put,
|
|
17
|
+
body: payload(body, fields).slice("muted"), repeatable: true, api_key:)
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
def set_photo(data, content_type: nil, api_key: nil)
|
|
21
|
+
request(ApiPaths::ACCOUNT_PHOTO, method: :put, raw: data, content_type: raw_content_type(data, content_type),
|
|
22
|
+
repeatable: true, api_key:)
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
def remove_photo(api_key: nil)
|
|
26
|
+
request(ApiPaths::ACCOUNT_PHOTO, method: :delete, repeatable: true, api_key:)
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
def get_username(api_key: nil)
|
|
30
|
+
request(ApiPaths::ACCOUNT_USERNAME, api_key:)
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
def check_username(username, api_key: nil)
|
|
34
|
+
request(ApiPaths::ACCOUNT_USERNAME_AVAILABILITY, query: query_params(username:), api_key:)
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
def set_username(username, api_key: nil)
|
|
38
|
+
request(ApiPaths::ACCOUNT_USERNAME, method: :put, body: {"username" => username}, api_key:)
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
def list_connected_apps(api_key: nil)
|
|
42
|
+
fetch_list(ApiPaths::ACCOUNT_CONNECTED_APPS, api_key:)
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
def revoke_connected_app(client_id, api_key: nil)
|
|
46
|
+
request(fill(ApiPaths::ACCOUNT_CONNECTED_APP, clientId: client_id), method: :delete, api_key:)
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
def list_invitations(api_key: nil)
|
|
50
|
+
fetch_list(ApiPaths::ACCOUNT_INVITATIONS, api_key:)
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
def accept_invitation(invitation_id, activate: nil, api_key: nil)
|
|
54
|
+
request(fill(ApiPaths::ACCOUNT_INVITATION_ACCEPT, invitationId: invitation_id), method: :post,
|
|
55
|
+
body: activate.nil? ? {} : {"activate" => activate}, repeatable: true, api_key:)
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
def decline_invitation(invitation_id, api_key: nil)
|
|
59
|
+
request(fill(ApiPaths::ACCOUNT_INVITATION_DECLINE, invitationId: invitation_id), method: :post,
|
|
60
|
+
repeatable: true, api_key:)
|
|
61
|
+
end
|
|
62
|
+
end
|
|
63
|
+
end
|
|
64
|
+
end
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module OpenEmail
|
|
4
|
+
module Resources
|
|
5
|
+
class Addresses < Resource
|
|
6
|
+
def list(limit: nil, cursor: nil, api_key: nil)
|
|
7
|
+
envelope = request(ApiPaths::ADDRESSES, query: Pagination.page_query(nil, CURSOR, limit, cursor), api_key:)
|
|
8
|
+
envelope = {} unless envelope.is_a?(Hash)
|
|
9
|
+
next_cursor = Pagination.cursor_of(envelope[:nextCursor])
|
|
10
|
+
|
|
11
|
+
AddressBookPage.new(
|
|
12
|
+
unrestricted: envelope[:unrestricted],
|
|
13
|
+
addresses: Pagination.items_of(envelope),
|
|
14
|
+
domains: envelope[:domains].is_a?(Array) ? envelope[:domains] : [],
|
|
15
|
+
has_more: Pagination.more?(envelope, next_cursor),
|
|
16
|
+
next_cursor:
|
|
17
|
+
)
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
def list_all(limit: nil, cursor: nil, api_key: nil)
|
|
21
|
+
current = list(limit:, cursor:, api_key:)
|
|
22
|
+
addresses = current.addresses.dup
|
|
23
|
+
|
|
24
|
+
while current.has_more && current.next_cursor && current.next_cursor != cursor
|
|
25
|
+
cursor = current.next_cursor
|
|
26
|
+
current = list(limit:, cursor:, api_key:)
|
|
27
|
+
addresses.concat(current.addresses)
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
AddressBook.new(unrestricted: current.unrestricted, addresses:, domains: current.domains)
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
def iterate(limit: nil, cursor: nil, api_key: nil, &)
|
|
34
|
+
iterate_pages(ApiPaths::ADDRESSES, limit:, cursor:, api_key:, &)
|
|
35
|
+
end
|
|
36
|
+
end
|
|
37
|
+
end
|
|
38
|
+
end
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module OpenEmail
|
|
4
|
+
module Resources
|
|
5
|
+
class Analytics < Resource
|
|
6
|
+
def mailbox(from: nil, to: nil, offset_minutes: nil, grain: nil, address: nil, api_key: nil)
|
|
7
|
+
query = query_params(from: instant(from), to: instant(to), offset_minutes:, grain:, address:)
|
|
8
|
+
request(ApiPaths::ANALYTICS_MAILBOX, query:, api_key:)
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
def sending(days: nil, minutes: nil, offset_minutes: nil, grain: nil, address: nil, api_key: nil)
|
|
12
|
+
query = query_params(days:, minutes:, offset_minutes:, grain:, address:)
|
|
13
|
+
request(ApiPaths::ANALYTICS_SENDING, query:, api_key:)
|
|
14
|
+
end
|
|
15
|
+
end
|
|
16
|
+
end
|
|
17
|
+
end
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module OpenEmail
|
|
4
|
+
module Resources
|
|
5
|
+
class AppHost < Resource
|
|
6
|
+
def get(api_key: nil)
|
|
7
|
+
request(ApiPaths::APP_HOST, api_key:)
|
|
8
|
+
end
|
|
9
|
+
|
|
10
|
+
def set(body = nil, api_key: nil, **fields)
|
|
11
|
+
request(ApiPaths::APP_HOST, method: :put, body: payload(body, fields), repeatable: true, api_key:)
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
def verify(api_key: nil)
|
|
15
|
+
request(ApiPaths::APP_HOST_VERIFY, method: :post, repeatable: true, api_key:)
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
def delete(api_key: nil)
|
|
19
|
+
request(ApiPaths::APP_HOST, method: :delete, repeatable: true, api_key:)
|
|
20
|
+
end
|
|
21
|
+
end
|
|
22
|
+
end
|
|
23
|
+
end
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module OpenEmail
|
|
4
|
+
module Resources
|
|
5
|
+
class Audiences < Resource
|
|
6
|
+
def list(limit: nil, cursor: nil, api_key: nil)
|
|
7
|
+
fetch_page(ApiPaths::AUDIENCES, limit:, cursor:, api_key:)
|
|
8
|
+
end
|
|
9
|
+
|
|
10
|
+
def list_all(limit: nil, cursor: nil, api_key: nil)
|
|
11
|
+
collect_all(ApiPaths::AUDIENCES, limit:, cursor:, api_key:)
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
def iterate(limit: nil, cursor: nil, api_key: nil, &)
|
|
15
|
+
iterate_pages(ApiPaths::AUDIENCES, limit:, cursor:, api_key:, &)
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
def growth(audience_ids: nil, days: nil, minutes: nil, grain: nil, offset_minutes: nil, api_key: nil)
|
|
19
|
+
query = query_params(audience_ids: joined(audience_ids), days:, minutes:, grain:, offset_minutes:)
|
|
20
|
+
request(ApiPaths::AUDIENCES_GROWTH, query:, api_key:)
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
def get(id, api_key: nil)
|
|
24
|
+
request(fill(ApiPaths::AUDIENCE, id:), api_key:)
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
def create(body = nil, api_key: nil, **fields)
|
|
28
|
+
request(ApiPaths::AUDIENCES, method: :post, body: payload(body, fields), api_key:)
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
def update(id, patch = nil, api_key: nil, **fields)
|
|
32
|
+
request(fill(ApiPaths::AUDIENCE, id:), method: :patch, body: payload(patch, fields), repeatable: true, api_key:)
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
def delete(id, api_key: nil)
|
|
36
|
+
request(fill(ApiPaths::AUDIENCE, id:), method: :delete, api_key:)
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
def empty(id, api_key: nil)
|
|
40
|
+
request(fill(ApiPaths::AUDIENCE_EMPTY, id:), method: :post, api_key:)
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
def list_contacts(id, q: nil, source: nil, sort: nil, statuses: nil, limit: nil, cursor: nil, api_key: nil)
|
|
44
|
+
fetch_page(fill(ApiPaths::AUDIENCE_CONTACTS, id:), member_query(q, source, sort, statuses), limit:, cursor:,
|
|
45
|
+
api_key:)
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
def list_all_contacts(id, q: nil, source: nil, sort: nil, statuses: nil, limit: nil, cursor: nil, api_key: nil)
|
|
49
|
+
collect_all(fill(ApiPaths::AUDIENCE_CONTACTS, id:), member_query(q, source, sort, statuses), limit:, cursor:,
|
|
50
|
+
api_key:)
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
def iterate_contacts(id, q: nil, source: nil, sort: nil, statuses: nil, limit: nil, cursor: nil, api_key: nil, &)
|
|
54
|
+
iterate_pages(fill(ApiPaths::AUDIENCE_CONTACTS, id:), member_query(q, source, sort, statuses), limit:, cursor:,
|
|
55
|
+
api_key:, &)
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
def add_contact(id, body = nil, api_key: nil, **fields)
|
|
59
|
+
request(fill(ApiPaths::AUDIENCE_CONTACTS, id:), method: :post, body: payload(body, fields), repeatable: true,
|
|
60
|
+
api_key:)
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
def remove_contact(id, email, api_key: nil)
|
|
64
|
+
request(fill(ApiPaths::AUDIENCE_CONTACT, id:, email:), method: :delete, api_key:)
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
def add_contacts(id, body = nil, api_key: nil, **fields)
|
|
68
|
+
request(fill(ApiPaths::AUDIENCE_CONTACTS_BATCH, id:), method: :post, body: payload(body, fields),
|
|
69
|
+
repeatable: true, api_key:)
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
def remove_contacts(id, body = nil, api_key: nil, **fields)
|
|
73
|
+
request(fill(ApiPaths::AUDIENCE_CONTACTS_BATCH_REMOVE, id:), method: :post, body: payload(body, fields),
|
|
74
|
+
repeatable: true, api_key:)
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
def import_contacts(id, body = nil, api_key: nil, **fields)
|
|
78
|
+
request(fill(ApiPaths::AUDIENCE_IMPORT, id:), method: :post, body: payload(body, fields), repeatable: true,
|
|
79
|
+
api_key:)
|
|
80
|
+
end
|
|
81
|
+
|
|
82
|
+
private
|
|
83
|
+
|
|
84
|
+
def member_query(q, source, sort, statuses)
|
|
85
|
+
status = (statuses.nil? || statuses.empty?) ? nil : joined(statuses)
|
|
86
|
+
query_params(q:, source:, sort:, status:)
|
|
87
|
+
end
|
|
88
|
+
end
|
|
89
|
+
end
|
|
90
|
+
end
|