sendly 3.37.0 → 3.38.0
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 +4 -4
- data/CHANGELOG.md +63 -1
- data/Gemfile.lock +2 -2
- data/README.md +180 -11
- data/lib/sendly/account_resource.rb +12 -8
- data/lib/sendly/business_upgrade_resource.rb +2 -0
- data/lib/sendly/client.rb +71 -4
- data/lib/sendly/enterprise.rb +2 -0
- data/lib/sendly/messages.rb +160 -13
- data/lib/sendly/rcs_resource.rb +222 -0
- data/lib/sendly/templates_resource.rb +169 -37
- data/lib/sendly/version.rb +1 -1
- data/lib/sendly/webhooks.rb +16 -0
- data/lib/sendly/whatsapp_resource.rb +655 -0
- data/lib/sendly.rb +2 -0
- data/sendly.gemspec +51 -0
- metadata +5 -2
|
@@ -2,41 +2,74 @@
|
|
|
2
2
|
|
|
3
3
|
module Sendly
|
|
4
4
|
class Template
|
|
5
|
-
attr_reader :id, :name, :
|
|
6
|
-
:
|
|
5
|
+
attr_reader :id, :name, :text, :variables, :is_preset, :preset_slug,
|
|
6
|
+
:status, :version, :published_at, :created_at, :updated_at
|
|
7
7
|
|
|
8
|
+
# @deprecated Templates are not scoped by locale. The API returns no
|
|
9
|
+
# locale field, so this is always nil. There is no replacement.
|
|
10
|
+
attr_reader :locale
|
|
11
|
+
|
|
12
|
+
# @deprecated The API has no concept of a default template. It returns no
|
|
13
|
+
# such field, so this is always false. There is no replacement.
|
|
14
|
+
attr_reader :is_default
|
|
15
|
+
|
|
16
|
+
STATUSES = %w[draft published].freeze
|
|
17
|
+
|
|
18
|
+
# @deprecated Preset and custom are distinguished by {#is_preset} /
|
|
19
|
+
# {#preset?} now. For the values {#status} can hold, see {STATUSES}.
|
|
8
20
|
TYPES = %w[preset custom].freeze
|
|
9
21
|
|
|
10
22
|
def initialize(data)
|
|
11
23
|
@id = data["id"]
|
|
12
24
|
@name = data["name"]
|
|
13
|
-
@
|
|
14
|
-
@type = data["type"] || "custom"
|
|
15
|
-
@locale = data["locale"]
|
|
25
|
+
@text = data["text"]
|
|
16
26
|
@variables = data["variables"] || []
|
|
27
|
+
@is_preset = data["is_preset"] || data["isPreset"] || false
|
|
28
|
+
@preset_slug = data["preset_slug"] || data["presetSlug"]
|
|
29
|
+
@status = data["status"]
|
|
30
|
+
@version = data["version"]
|
|
31
|
+
@published_at = parse_time(data["published_at"] || data["publishedAt"])
|
|
32
|
+
@created_at = parse_time(data["created_at"] || data["createdAt"])
|
|
33
|
+
@updated_at = parse_time(data["updated_at"] || data["updatedAt"])
|
|
34
|
+
@locale = data["locale"]
|
|
17
35
|
@is_default = data["isDefault"] || data["is_default"] || false
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
alias body text
|
|
39
|
+
|
|
40
|
+
# @deprecated Use {#is_preset} or {#preset?}. Derived from {#is_preset}:
|
|
41
|
+
# "preset" for a preset template, "custom" for your own.
|
|
42
|
+
# @return [String]
|
|
43
|
+
def type
|
|
44
|
+
is_preset ? "preset" : "custom"
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
# @deprecated Use {#status} or {#published?}. Derived from {#status}.
|
|
48
|
+
# @return [Boolean]
|
|
49
|
+
def is_published
|
|
50
|
+
status == "published"
|
|
21
51
|
end
|
|
22
52
|
|
|
23
53
|
def preset?
|
|
24
|
-
|
|
54
|
+
is_preset
|
|
25
55
|
end
|
|
26
56
|
|
|
27
57
|
def custom?
|
|
28
|
-
|
|
58
|
+
!is_preset
|
|
29
59
|
end
|
|
30
60
|
|
|
31
61
|
def published?
|
|
32
|
-
|
|
62
|
+
status == "published"
|
|
33
63
|
end
|
|
34
64
|
|
|
35
65
|
def to_h
|
|
36
66
|
{
|
|
37
|
-
id: id, name: name,
|
|
38
|
-
|
|
39
|
-
|
|
67
|
+
id: id, name: name, text: text, variables: variables,
|
|
68
|
+
is_preset: is_preset, preset_slug: preset_slug, status: status,
|
|
69
|
+
version: version, published_at: published_at&.iso8601,
|
|
70
|
+
created_at: created_at&.iso8601, updated_at: updated_at&.iso8601,
|
|
71
|
+
body: body, type: type, locale: locale, is_default: is_default,
|
|
72
|
+
is_published: is_published
|
|
40
73
|
}.compact
|
|
41
74
|
end
|
|
42
75
|
|
|
@@ -55,56 +88,144 @@ module Sendly
|
|
|
55
88
|
@client = client
|
|
56
89
|
end
|
|
57
90
|
|
|
91
|
+
# List templates visible to the API key, presets included.
|
|
92
|
+
#
|
|
93
|
+
# @param limit [Integer, nil] Deprecated and unsupported. The list route
|
|
94
|
+
# returns every visible template in one response and does not paginate.
|
|
95
|
+
# Passing a value raises ArgumentError; slice the returned +:templates+
|
|
96
|
+
# array instead.
|
|
97
|
+
# @param type [String, nil] Deprecated and unsupported. The list route does
|
|
98
|
+
# not filter. Passing a value raises ArgumentError; select over the
|
|
99
|
+
# returned +:templates+ with {Template#preset?} / {Template#custom?}
|
|
100
|
+
# instead.
|
|
101
|
+
# @param locale [String, nil] Deprecated and unsupported. Templates are not
|
|
102
|
+
# scoped by locale. Passing a value raises ArgumentError; there is no
|
|
103
|
+
# replacement.
|
|
104
|
+
# @raise [ArgumentError] if +limit+, +type+ or +locale+ is given
|
|
105
|
+
# @return [Hash] +:templates+ (Array<Sendly::Template>) and +:pagination+,
|
|
106
|
+
# which is always nil because the route does not paginate
|
|
58
107
|
def list(limit: nil, type: nil, locale: nil)
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
108
|
+
unless limit.nil?
|
|
109
|
+
raise ArgumentError,
|
|
110
|
+
"limit is not supported: the templates list route returns every " \
|
|
111
|
+
"visible template in one response and does not paginate. Slice " \
|
|
112
|
+
"the returned :templates array instead."
|
|
113
|
+
end
|
|
114
|
+
|
|
115
|
+
unless type.nil?
|
|
116
|
+
raise ArgumentError,
|
|
117
|
+
"type is not supported: the templates list route does not filter. " \
|
|
118
|
+
"Select over the returned :templates array with " \
|
|
119
|
+
"Sendly::Template#preset? or #custom? instead."
|
|
120
|
+
end
|
|
121
|
+
|
|
122
|
+
reject_locale!(locale)
|
|
123
|
+
|
|
124
|
+
response = @client.get("/templates")
|
|
125
|
+
{
|
|
126
|
+
templates: (response["templates"] || []).map { |t| Template.new(t) },
|
|
127
|
+
pagination: response["pagination"]
|
|
128
|
+
}
|
|
67
129
|
end
|
|
68
130
|
|
|
69
131
|
def get(id)
|
|
70
|
-
response = @client.get("/
|
|
132
|
+
response = @client.get("/templates/#{id}")
|
|
71
133
|
Template.new(response)
|
|
72
134
|
end
|
|
73
135
|
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
136
|
+
# Create a template. New templates start as drafts; call {#publish} to
|
|
137
|
+
# make one usable.
|
|
138
|
+
#
|
|
139
|
+
# @param name [String] Template name
|
|
140
|
+
# @param text [String] Template text, with +{{variable}}+ placeholders
|
|
141
|
+
# @param body [String, nil] Deprecated alias for +text+. Sent as +text+.
|
|
142
|
+
# Pass +text+.
|
|
143
|
+
# @param locale [String, nil] Deprecated and unsupported. Templates are not
|
|
144
|
+
# scoped by locale. Passing a value raises ArgumentError; there is no
|
|
145
|
+
# replacement.
|
|
146
|
+
# @param is_published [Boolean, nil] Deprecated. Templates are always
|
|
147
|
+
# created as drafts, so +false+ is accepted as a no-op and +true+ raises
|
|
148
|
+
# ArgumentError. Call {#publish} on the new template instead.
|
|
149
|
+
# @raise [ArgumentError] if +locale+ is given, if +is_published+ is true, or
|
|
150
|
+
# if neither +text+ nor +body+ is given
|
|
151
|
+
# @return [Sendly::Template]
|
|
152
|
+
def create(name:, text: nil, body: nil, locale: nil, is_published: nil)
|
|
153
|
+
reject_locale!(locale)
|
|
154
|
+
|
|
155
|
+
if is_published
|
|
156
|
+
raise ArgumentError,
|
|
157
|
+
"is_published: true is not supported on create: templates are " \
|
|
158
|
+
"always created as drafts. Publishing is a separate call - use " \
|
|
159
|
+
"publish(id) on the returned template instead."
|
|
160
|
+
end
|
|
161
|
+
|
|
162
|
+
content = text || body
|
|
163
|
+
raise ArgumentError, "text is required" if content.nil?
|
|
164
|
+
|
|
165
|
+
response = @client.post("/templates", { name: name, text: content })
|
|
80
166
|
Template.new(response)
|
|
81
167
|
end
|
|
82
168
|
|
|
83
|
-
|
|
169
|
+
# Update a draft template. Published templates cannot be edited; publish a
|
|
170
|
+
# new template instead.
|
|
171
|
+
#
|
|
172
|
+
# @param id [String] Template ID
|
|
173
|
+
# @param name [String, nil] New name
|
|
174
|
+
# @param text [String, nil] New template text
|
|
175
|
+
# @param body [String, nil] Deprecated alias for +text+. Sent as +text+.
|
|
176
|
+
# Pass +text+.
|
|
177
|
+
# @param locale [String, nil] Deprecated and unsupported. Templates are not
|
|
178
|
+
# scoped by locale. Passing a value raises ArgumentError; there is no
|
|
179
|
+
# replacement.
|
|
180
|
+
# @param is_published [Boolean, nil] Deprecated. An update never changes a
|
|
181
|
+
# template's status, and only drafts are editable, so +false+ is accepted
|
|
182
|
+
# as a no-op and +true+ raises ArgumentError. Call {#publish} instead.
|
|
183
|
+
# @raise [ArgumentError] if +locale+ is given or +is_published+ is true
|
|
184
|
+
# @return [Sendly::Template]
|
|
185
|
+
def update(id, name: nil, text: nil, body: nil, locale: nil, is_published: nil)
|
|
186
|
+
reject_locale!(locale)
|
|
187
|
+
|
|
188
|
+
if is_published
|
|
189
|
+
raise ArgumentError,
|
|
190
|
+
"is_published: true is not supported on update: an update never " \
|
|
191
|
+
"changes a template's status. Publishing is a separate call - " \
|
|
192
|
+
"use publish(id) instead."
|
|
193
|
+
end
|
|
194
|
+
|
|
195
|
+
content = text || body
|
|
196
|
+
|
|
84
197
|
request_body = {}
|
|
85
198
|
request_body[:name] = name if name
|
|
86
|
-
request_body[:
|
|
87
|
-
request_body[:locale] = locale if locale
|
|
88
|
-
request_body[:isPublished] = is_published unless is_published.nil?
|
|
199
|
+
request_body[:text] = content if content
|
|
89
200
|
|
|
90
|
-
response = @client.patch("/
|
|
201
|
+
response = @client.patch("/templates/#{id}", request_body)
|
|
91
202
|
Template.new(response)
|
|
92
203
|
end
|
|
93
204
|
|
|
94
205
|
def delete(id)
|
|
95
|
-
@client.delete("/
|
|
206
|
+
@client.delete("/templates/#{id}")
|
|
96
207
|
end
|
|
97
208
|
|
|
98
209
|
def publish(id)
|
|
99
|
-
response = @client.post("/
|
|
210
|
+
response = @client.post("/templates/#{id}/publish")
|
|
100
211
|
Template.new(response)
|
|
101
212
|
end
|
|
102
213
|
|
|
214
|
+
# Unpublish a published template.
|
|
215
|
+
#
|
|
216
|
+
# @note Not available yet: the versioned API serves no unpublish route, so
|
|
217
|
+
# this call fails with a 404. To retire a published template today,
|
|
218
|
+
# {#create} and {#publish} a replacement, then {#delete} this one.
|
|
103
219
|
def unpublish(id)
|
|
104
|
-
response = @client.post("/
|
|
220
|
+
response = @client.post("/templates/#{id}/unpublish")
|
|
105
221
|
Template.new(response)
|
|
106
222
|
end
|
|
107
223
|
|
|
224
|
+
# Copy an existing template into a new draft.
|
|
225
|
+
#
|
|
226
|
+
# @note Not available yet: the versioned API serves no clone route, so
|
|
227
|
+
# this call fails with a 404. To copy a template today, read it with
|
|
228
|
+
# {#get} and pass its {Template#text} to {#create}.
|
|
108
229
|
def clone(id, name: nil)
|
|
109
230
|
body = {}
|
|
110
231
|
body[:name] = name if name
|
|
@@ -118,5 +239,16 @@ module Sendly
|
|
|
118
239
|
response = @client.post("/templates/generate", body)
|
|
119
240
|
GeneratedTemplate.new(response)
|
|
120
241
|
end
|
|
242
|
+
|
|
243
|
+
private
|
|
244
|
+
|
|
245
|
+
def reject_locale!(locale)
|
|
246
|
+
return if locale.nil?
|
|
247
|
+
|
|
248
|
+
raise ArgumentError,
|
|
249
|
+
"locale is not supported: templates are not scoped by locale and " \
|
|
250
|
+
"the API stores no locale field. There is no replacement - keep " \
|
|
251
|
+
"per-locale wording in separate templates."
|
|
252
|
+
end
|
|
121
253
|
end
|
|
122
254
|
end
|
data/lib/sendly/version.rb
CHANGED
data/lib/sendly/webhooks.rb
CHANGED
|
@@ -56,6 +56,22 @@ module Sendly
|
|
|
56
56
|
EVENT_CONTACT_MARKED_VALID = "contact.marked_valid"
|
|
57
57
|
EVENT_CONTACTS_LOOKUP_COMPLETED = "contacts.lookup_completed"
|
|
58
58
|
EVENT_CONTACTS_BULK_MARKED_VALID = "contacts.bulk_marked_valid"
|
|
59
|
+
EVENT_BRAND_VERIFIED = "brand.verified"
|
|
60
|
+
EVENT_BRAND_FAILED = "brand.failed"
|
|
61
|
+
EVENT_CAMPAIGN_APPROVED = "campaign.approved"
|
|
62
|
+
EVENT_CAMPAIGN_REJECTED = "campaign.rejected"
|
|
63
|
+
EVENT_CAMPAIGN_SUSPENDED = "campaign.suspended"
|
|
64
|
+
EVENT_ASSIGNMENT_CONFIRMED = "assignment.confirmed"
|
|
65
|
+
EVENT_ASSIGNMENT_FAILED = "assignment.failed"
|
|
66
|
+
EVENT_PORT_COMPLETED = "port.completed"
|
|
67
|
+
EVENT_PORT_OUT_REQUESTED = "port_out.requested"
|
|
68
|
+
EVENT_PORT_OUT_COMPLETED = "port_out.completed"
|
|
69
|
+
EVENT_PORT_OUT_REJECTED = "port_out.rejected"
|
|
70
|
+
EVENT_PORT_OUT_CANCELLED = "port_out.cancelled"
|
|
71
|
+
EVENT_NUMBER_ACTIVATED = "number.activated"
|
|
72
|
+
EVENT_NUMBER_FAILED = "number.failed"
|
|
73
|
+
EVENT_NUMBER_REQUIREMENTS_REQUIRED = "number.requirements_required"
|
|
74
|
+
EVENT_NUMBER_RELEASED = "number.released"
|
|
59
75
|
|
|
60
76
|
# Source of a list-health event. Frozen enum — new values will be
|
|
61
77
|
# added in minor SDK versions, never removed.
|