fopost 0.1.0 → 0.3.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 +181 -0
- data/README.md +645 -2
- data/lib/fopost/client.rb +33 -1
- data/lib/fopost/errors.rb +25 -0
- data/lib/fopost/file_part.rb +73 -0
- data/lib/fopost/http/client.rb +36 -4
- data/lib/fopost/http/multipart.rb +38 -0
- data/lib/fopost/models.rb +2104 -0
- data/lib/fopost/platforms.rb +27 -0
- data/lib/fopost/resources/account_groups.rb +37 -0
- data/lib/fopost/resources/accounts.rb +445 -3
- data/lib/fopost/resources/activity.rb +27 -0
- data/lib/fopost/resources/ads.rb +1097 -0
- data/lib/fopost/resources/analytics.rb +63 -0
- data/lib/fopost/resources/blog.rb +206 -0
- data/lib/fopost/resources/blogs.rb +115 -0
- data/lib/fopost/resources/broadcasts.rb +189 -0
- data/lib/fopost/resources/contacts.rb +136 -0
- data/lib/fopost/resources/google_ads.rb +346 -0
- data/lib/fopost/resources/google_business.rb +166 -0
- data/lib/fopost/resources/inbox.rb +224 -0
- data/lib/fopost/resources/knowledge.rb +74 -0
- data/lib/fopost/resources/media.rb +56 -0
- data/lib/fopost/resources/posts.rb +75 -4
- data/lib/fopost/resources/validate.rb +37 -0
- data/lib/fopost/resources/whatsapp.rb +255 -0
- data/lib/fopost/version.rb +1 -1
- data/lib/fopost/webhooks.rb +97 -0
- data/lib/fopost.rb +2 -0
- metadata +19 -1
|
@@ -0,0 +1,255 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Fopost
|
|
4
|
+
module Resources
|
|
5
|
+
# `client.whatsapp` — a WhatsApp Business connection.
|
|
6
|
+
#
|
|
7
|
+
# The platform owns templates, flows, the profile and the commerce settings,
|
|
8
|
+
# so every method here is a live read or write against the customer's own
|
|
9
|
+
# WhatsApp Business Account. All of it answers 503 until WhatsApp is set up.
|
|
10
|
+
class Whatsapp < Base
|
|
11
|
+
# ─── Profile ─────────────────────────────────────────────────
|
|
12
|
+
|
|
13
|
+
def profile(account_id)
|
|
14
|
+
WhatsappProfile.new(unwrap(http.get("/accounts/#{account_id}/whatsapp/profile")))
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
def update_profile(account_id, about: UNSET, address: UNSET, description: UNSET,
|
|
18
|
+
vertical: UNSET, websites: UNSET, profile_picture_media_id: UNSET)
|
|
19
|
+
body = compact_unset(
|
|
20
|
+
'about' => about,
|
|
21
|
+
'address' => address,
|
|
22
|
+
'description' => description,
|
|
23
|
+
'vertical' => vertical,
|
|
24
|
+
'websites' => websites,
|
|
25
|
+
'profile_picture_media_id' => profile_picture_media_id
|
|
26
|
+
)
|
|
27
|
+
WhatsappProfile.new(unwrap(http.request(:patch, "/accounts/#{account_id}/whatsapp/profile", json: body)))
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
# A review, not a write: the number keeps its old name until it passes.
|
|
31
|
+
def request_display_name(account_id, display_name)
|
|
32
|
+
body = { 'display_name' => display_name }
|
|
33
|
+
as_hash(unwrap(http.post("/accounts/#{account_id}/whatsapp/profile/display-name", body)))
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
def set_username(account_id, username)
|
|
37
|
+
body = { 'username' => username }
|
|
38
|
+
WhatsappProfile.new(unwrap(http.put("/accounts/#{account_id}/whatsapp/profile/username", body)))
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
# ─── Templates ───────────────────────────────────────────────
|
|
42
|
+
|
|
43
|
+
def templates(account_id, after: nil)
|
|
44
|
+
params = compact_nil('after' => after)
|
|
45
|
+
parse_list(WhatsappTemplate, unwrap(http.get("/accounts/#{account_id}/whatsapp/templates", params)))
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
# The platform's pre-written templates, for adapting instead of drafting.
|
|
49
|
+
def template_library(account_id, search: nil)
|
|
50
|
+
params = compact_nil('search' => search)
|
|
51
|
+
body = unwrap(http.get("/accounts/#{account_id}/whatsapp/templates/library", params))
|
|
52
|
+
body.is_a?(Array) ? body : []
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
def template(account_id, template_id)
|
|
56
|
+
WhatsappTemplate.new(unwrap(http.get("/accounts/#{account_id}/whatsapp/templates/#{template_id}")))
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
# Files a template for review; the result carries the status it was given.
|
|
60
|
+
def create_template(account_id, name:, language:, category:, components:,
|
|
61
|
+
allow_category_change: nil)
|
|
62
|
+
body = compact_nil(
|
|
63
|
+
'name' => name,
|
|
64
|
+
'language' => language,
|
|
65
|
+
'category' => category,
|
|
66
|
+
'components' => components.map { |c| c.transform_keys(&:to_s) },
|
|
67
|
+
'allow_category_change' => allow_category_change
|
|
68
|
+
)
|
|
69
|
+
WhatsappTemplate.new(unwrap(http.post("/accounts/#{account_id}/whatsapp/templates", body)))
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
def import_template(account_id, library_template_name:, name:, language:, category:,
|
|
73
|
+
library_template_button_inputs: nil)
|
|
74
|
+
body = compact_nil(
|
|
75
|
+
'library_template_name' => library_template_name,
|
|
76
|
+
'name' => name,
|
|
77
|
+
'language' => language,
|
|
78
|
+
'category' => category,
|
|
79
|
+
'library_template_button_inputs' => library_template_button_inputs
|
|
80
|
+
)
|
|
81
|
+
WhatsappTemplate.new(unwrap(http.post("/accounts/#{account_id}/whatsapp/templates/import", body)))
|
|
82
|
+
end
|
|
83
|
+
|
|
84
|
+
def update_template(account_id, template_id, category: nil, components: nil)
|
|
85
|
+
body = compact_nil('category' => category, 'components' => components)
|
|
86
|
+
path = "/accounts/#{account_id}/whatsapp/templates/#{template_id}"
|
|
87
|
+
WhatsappTemplate.new(unwrap(http.request(:patch, path, json: body)))
|
|
88
|
+
end
|
|
89
|
+
|
|
90
|
+
# The name is required: it is what the platform deletes by.
|
|
91
|
+
def delete_template(account_id, template_id, name:)
|
|
92
|
+
query = URI.encode_www_form('name' => name)
|
|
93
|
+
as_hash(unwrap(http.delete("/accounts/#{account_id}/whatsapp/templates/#{template_id}?#{query}")))
|
|
94
|
+
end
|
|
95
|
+
|
|
96
|
+
# ─── Groups ──────────────────────────────────────────────────
|
|
97
|
+
|
|
98
|
+
def groups(account_id)
|
|
99
|
+
parse_list(WhatsappGroup, unwrap(http.get("/accounts/#{account_id}/whatsapp/groups")))
|
|
100
|
+
end
|
|
101
|
+
|
|
102
|
+
# Participation is invite-only: send the invite link, there is no add.
|
|
103
|
+
def create_group(account_id, subject:, description: nil)
|
|
104
|
+
body = compact_nil('subject' => subject, 'description' => description)
|
|
105
|
+
WhatsappGroup.new(unwrap(http.post("/accounts/#{account_id}/whatsapp/groups", body)))
|
|
106
|
+
end
|
|
107
|
+
|
|
108
|
+
def group(account_id, group_id)
|
|
109
|
+
WhatsappGroup.new(unwrap(http.get("/accounts/#{account_id}/whatsapp/groups/#{group_id}")))
|
|
110
|
+
end
|
|
111
|
+
|
|
112
|
+
def update_group(account_id, group_id, subject: nil, description: nil)
|
|
113
|
+
body = compact_nil('subject' => subject, 'description' => description)
|
|
114
|
+
path = "/accounts/#{account_id}/whatsapp/groups/#{group_id}"
|
|
115
|
+
WhatsappGroup.new(unwrap(http.request(:patch, path, json: body)))
|
|
116
|
+
end
|
|
117
|
+
|
|
118
|
+
def delete_group(account_id, group_id)
|
|
119
|
+
as_hash(unwrap(http.delete("/accounts/#{account_id}/whatsapp/groups/#{group_id}")))
|
|
120
|
+
end
|
|
121
|
+
|
|
122
|
+
def group_invite_link(account_id, group_id)
|
|
123
|
+
body = as_hash(unwrap(http.get("/accounts/#{account_id}/whatsapp/groups/#{group_id}/invite-link")))
|
|
124
|
+
body['inviteLink']
|
|
125
|
+
end
|
|
126
|
+
|
|
127
|
+
# Issues a new link and invalidates the old one.
|
|
128
|
+
def reset_group_invite_link(account_id, group_id)
|
|
129
|
+
body = as_hash(unwrap(http.post("/accounts/#{account_id}/whatsapp/groups/#{group_id}/invite-link")))
|
|
130
|
+
body['inviteLink']
|
|
131
|
+
end
|
|
132
|
+
|
|
133
|
+
def remove_group_participants(account_id, group_id, users)
|
|
134
|
+
path = "/accounts/#{account_id}/whatsapp/groups/#{group_id}/participants"
|
|
135
|
+
as_hash(unwrap(http.delete(path, { 'users' => users.to_a })))
|
|
136
|
+
end
|
|
137
|
+
|
|
138
|
+
# ─── Blocking ────────────────────────────────────────────────
|
|
139
|
+
|
|
140
|
+
def blocked(account_id, after: nil)
|
|
141
|
+
params = compact_nil('after' => after)
|
|
142
|
+
body = unwrap(http.get("/accounts/#{account_id}/whatsapp/block", params))
|
|
143
|
+
body.is_a?(Array) ? body : []
|
|
144
|
+
end
|
|
145
|
+
|
|
146
|
+
def block_users(account_id, users)
|
|
147
|
+
body = { 'users' => users.to_a }
|
|
148
|
+
WhatsappBlockResult.new(unwrap(http.post("/accounts/#{account_id}/whatsapp/block", body)))
|
|
149
|
+
end
|
|
150
|
+
|
|
151
|
+
def unblock_users(account_id, users)
|
|
152
|
+
body = { 'users' => users.to_a }
|
|
153
|
+
WhatsappBlockResult.new(unwrap(http.delete("/accounts/#{account_id}/whatsapp/block", body)))
|
|
154
|
+
end
|
|
155
|
+
|
|
156
|
+
# ─── Commerce ────────────────────────────────────────────────
|
|
157
|
+
|
|
158
|
+
def commerce_settings(account_id)
|
|
159
|
+
WhatsappCommerceSettings.new(unwrap(http.get("/accounts/#{account_id}/whatsapp/commerce")))
|
|
160
|
+
end
|
|
161
|
+
|
|
162
|
+
def update_commerce_settings(account_id, cart_enabled: nil, catalog_visible: nil)
|
|
163
|
+
body = compact_nil('is_cart_enabled' => cart_enabled, 'is_catalog_visible' => catalog_visible)
|
|
164
|
+
path = "/accounts/#{account_id}/whatsapp/commerce"
|
|
165
|
+
WhatsappCommerceSettings.new(unwrap(http.request(:patch, path, json: body)))
|
|
166
|
+
end
|
|
167
|
+
|
|
168
|
+
def link_catalog(account_id, catalog_id)
|
|
169
|
+
body = { 'catalog_id' => catalog_id }
|
|
170
|
+
path = "/accounts/#{account_id}/whatsapp/commerce/catalog"
|
|
171
|
+
WhatsappCommerceSettings.new(unwrap(http.post(path, body)))
|
|
172
|
+
end
|
|
173
|
+
|
|
174
|
+
# ─── Flows ───────────────────────────────────────────────────
|
|
175
|
+
|
|
176
|
+
def flows(account_id)
|
|
177
|
+
parse_list(WhatsappFlow, unwrap(http.get("/accounts/#{account_id}/whatsapp/flows")))
|
|
178
|
+
end
|
|
179
|
+
|
|
180
|
+
def flow(account_id, flow_id)
|
|
181
|
+
WhatsappFlow.new(unwrap(http.get("/accounts/#{account_id}/whatsapp/flows/#{flow_id}")))
|
|
182
|
+
end
|
|
183
|
+
|
|
184
|
+
def create_flow(account_id, name:, categories:, endpoint_uri: nil, clone_flow_id: nil)
|
|
185
|
+
body = compact_nil(
|
|
186
|
+
'name' => name,
|
|
187
|
+
'categories' => categories.to_a,
|
|
188
|
+
'endpoint_uri' => endpoint_uri,
|
|
189
|
+
'clone_flow_id' => clone_flow_id
|
|
190
|
+
)
|
|
191
|
+
WhatsappFlow.new(unwrap(http.post("/accounts/#{account_id}/whatsapp/flows", body)))
|
|
192
|
+
end
|
|
193
|
+
|
|
194
|
+
def update_flow(account_id, flow_id, name: nil, categories: nil, endpoint_uri: nil)
|
|
195
|
+
body = compact_nil('name' => name, 'categories' => categories, 'endpoint_uri' => endpoint_uri)
|
|
196
|
+
path = "/accounts/#{account_id}/whatsapp/flows/#{flow_id}"
|
|
197
|
+
WhatsappFlow.new(unwrap(http.request(:patch, path, json: body)))
|
|
198
|
+
end
|
|
199
|
+
|
|
200
|
+
# Drafts only; a published flow is deprecated instead.
|
|
201
|
+
def delete_flow(account_id, flow_id)
|
|
202
|
+
as_hash(unwrap(http.delete("/accounts/#{account_id}/whatsapp/flows/#{flow_id}")))
|
|
203
|
+
end
|
|
204
|
+
|
|
205
|
+
# The platform answers with its validation errors rather than refusing.
|
|
206
|
+
def upload_flow_json(account_id, flow_id, flow_json)
|
|
207
|
+
body = { 'flow_json' => flow_json }
|
|
208
|
+
path = "/accounts/#{account_id}/whatsapp/flows/#{flow_id}/json"
|
|
209
|
+
WhatsappFlowJsonResult.new(unwrap(http.put(path, body)))
|
|
210
|
+
end
|
|
211
|
+
|
|
212
|
+
def publish_flow(account_id, flow_id)
|
|
213
|
+
WhatsappFlow.new(unwrap(http.post("/accounts/#{account_id}/whatsapp/flows/#{flow_id}/publish")))
|
|
214
|
+
end
|
|
215
|
+
|
|
216
|
+
def deprecate_flow(account_id, flow_id)
|
|
217
|
+
WhatsappFlow.new(unwrap(http.post("/accounts/#{account_id}/whatsapp/flows/#{flow_id}/deprecate")))
|
|
218
|
+
end
|
|
219
|
+
|
|
220
|
+
def flow_responses(account_id)
|
|
221
|
+
path = "/accounts/#{account_id}/whatsapp/flows/responses"
|
|
222
|
+
parse_list(WhatsappFlowResponse, unwrap(http.get(path)))
|
|
223
|
+
end
|
|
224
|
+
|
|
225
|
+
def encryption_key_status(account_id)
|
|
226
|
+
path = "/accounts/#{account_id}/whatsapp/flows/encryption-key"
|
|
227
|
+
WhatsappEncryptionKeyStatus.new(unwrap(http.get(path)))
|
|
228
|
+
end
|
|
229
|
+
|
|
230
|
+
# The public half only; the private half stays with the customer.
|
|
231
|
+
def set_encryption_key(account_id, business_public_key)
|
|
232
|
+
body = { 'business_public_key' => business_public_key }
|
|
233
|
+
path = "/accounts/#{account_id}/whatsapp/flows/encryption-key"
|
|
234
|
+
WhatsappEncryptionKeyStatus.new(unwrap(http.put(path, body)))
|
|
235
|
+
end
|
|
236
|
+
|
|
237
|
+
# ─── Account state and sandbox ───────────────────────────────
|
|
238
|
+
|
|
239
|
+
def account_events(account_id)
|
|
240
|
+
as_hash(unwrap(http.get("/accounts/#{account_id}/whatsapp/events")))
|
|
241
|
+
end
|
|
242
|
+
|
|
243
|
+
def sandbox_sessions(workspace_id:)
|
|
244
|
+
params = { 'workspaceId' => workspace_id }
|
|
245
|
+
parse_list(WhatsappSandboxSession, unwrap(http.get('/whatsapp/sandbox/sessions', params)))
|
|
246
|
+
end
|
|
247
|
+
|
|
248
|
+
# Sends a template from the platform-owned test number; needs the publish scope.
|
|
249
|
+
def create_sandbox_session(workspace_id:, phone_number:)
|
|
250
|
+
body = { 'workspaceId' => workspace_id, 'phoneNumber' => phone_number }
|
|
251
|
+
WhatsappSandboxSession.new(unwrap(http.post('/whatsapp/sandbox/sessions', body)))
|
|
252
|
+
end
|
|
253
|
+
end
|
|
254
|
+
end
|
|
255
|
+
end
|
data/lib/fopost/version.rb
CHANGED
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'json'
|
|
4
|
+
require 'openssl'
|
|
5
|
+
require 'time'
|
|
6
|
+
|
|
7
|
+
require 'fopost/models'
|
|
8
|
+
|
|
9
|
+
module Fopost
|
|
10
|
+
# Raised when a webhook delivery cannot be trusted.
|
|
11
|
+
class WebhookSignatureError < StandardError; end
|
|
12
|
+
|
|
13
|
+
# Verifies the signature FoPost puts on every outbound webhook, including
|
|
14
|
+
# the website target of the blog.
|
|
15
|
+
#
|
|
16
|
+
# event = Fopost::Webhooks.verify(request.raw_post, request.headers, secret)
|
|
17
|
+
# event.article.title if event.event == 'article.published'
|
|
18
|
+
#
|
|
19
|
+
# Verify the raw request bytes, never re-serialised JSON: key order and
|
|
20
|
+
# whitespace change the bytes and the signature no longer matches.
|
|
21
|
+
module Webhooks
|
|
22
|
+
SIGNATURE_HEADER = 'x-fopost-signature'
|
|
23
|
+
TIMESTAMPED_SIGNATURE_HEADER = 'x-fopost-signature-256'
|
|
24
|
+
TIMESTAMP_HEADER = 'x-fopost-timestamp'
|
|
25
|
+
EVENT_HEADER = 'x-fopost-event'
|
|
26
|
+
DELIVERY_HEADER = 'x-fopost-delivery'
|
|
27
|
+
DEFAULT_TOLERANCE = 300
|
|
28
|
+
|
|
29
|
+
module_function
|
|
30
|
+
|
|
31
|
+
# Returns the parsed envelope as a {Fopost::WebhookEvent}, or raises
|
|
32
|
+
# {Fopost::WebhookSignatureError}.
|
|
33
|
+
#
|
|
34
|
+
# The timestamped signature is checked when `X-FoPost-Timestamp` is
|
|
35
|
+
# present and rejected when it is older than `tolerance` seconds; the
|
|
36
|
+
# plain body signature is accepted only when no timestamp was sent.
|
|
37
|
+
# `headers` may use any casing; a Rack `HTTP_X_FOPOST_*` key also works.
|
|
38
|
+
def verify(raw_body, headers, secret, tolerance: DEFAULT_TOLERANCE, now: nil)
|
|
39
|
+
raise WebhookSignatureError, 'fopost: a webhook secret is required' if secret.nil? || secret.to_s.empty?
|
|
40
|
+
|
|
41
|
+
body = raw_body.to_s
|
|
42
|
+
lookup = normalize_headers(headers)
|
|
43
|
+
timestamp = lookup[TIMESTAMP_HEADER]
|
|
44
|
+
|
|
45
|
+
if timestamp
|
|
46
|
+
check_timestamp(timestamp, tolerance, now)
|
|
47
|
+
expected = sign("#{timestamp}.#{body}", secret)
|
|
48
|
+
match = secure_equal(lookup[TIMESTAMPED_SIGNATURE_HEADER], expected)
|
|
49
|
+
else
|
|
50
|
+
match = secure_equal(lookup[SIGNATURE_HEADER], sign(body, secret))
|
|
51
|
+
end
|
|
52
|
+
raise WebhookSignatureError, 'fopost: webhook signature mismatch' unless match
|
|
53
|
+
|
|
54
|
+
parse(body)
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
# The header value FoPost sends for these exact bytes.
|
|
58
|
+
def sign(payload, secret)
|
|
59
|
+
"sha256=#{OpenSSL::HMAC.hexdigest('SHA256', secret.to_s, payload)}"
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
def check_timestamp(timestamp, tolerance, now)
|
|
63
|
+
seconds = Integer(timestamp.to_s.strip, exception: false)
|
|
64
|
+
raise WebhookSignatureError, 'fopost: webhook timestamp is not a number' unless seconds
|
|
65
|
+
|
|
66
|
+
drift = ((now || Time.now).to_i - seconds).abs
|
|
67
|
+
raise WebhookSignatureError, 'fopost: webhook timestamp outside tolerance' if drift > tolerance
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
def secure_equal(given, expected)
|
|
71
|
+
return false if given.nil?
|
|
72
|
+
|
|
73
|
+
given = given.to_s.strip
|
|
74
|
+
return false unless given.bytesize == expected.bytesize
|
|
75
|
+
|
|
76
|
+
OpenSSL.fixed_length_secure_compare(given, expected)
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
def parse(body)
|
|
80
|
+
data = JSON.parse(body)
|
|
81
|
+
raise WebhookSignatureError, 'fopost: webhook body is not an object' unless data.is_a?(Hash)
|
|
82
|
+
|
|
83
|
+
WebhookEvent.new(data)
|
|
84
|
+
rescue JSON::ParserError
|
|
85
|
+
raise WebhookSignatureError, 'fopost: webhook body is not JSON'
|
|
86
|
+
end
|
|
87
|
+
|
|
88
|
+
def normalize_headers(headers)
|
|
89
|
+
(headers || {}).each_with_object({}) do |(key, value), out|
|
|
90
|
+
name = key.to_s.downcase.tr('_', '-').sub(/\Ahttp-/, '')
|
|
91
|
+
out[name] = value.is_a?(Array) ? value.first : value
|
|
92
|
+
end
|
|
93
|
+
end
|
|
94
|
+
|
|
95
|
+
private_class_method :check_timestamp, :secure_equal, :parse, :normalize_headers
|
|
96
|
+
end
|
|
97
|
+
end
|
data/lib/fopost.rb
CHANGED
|
@@ -6,11 +6,13 @@ require 'fopost/unset'
|
|
|
6
6
|
require 'fopost/platforms'
|
|
7
7
|
require 'fopost/model'
|
|
8
8
|
require 'fopost/models'
|
|
9
|
+
require 'fopost/file_part'
|
|
9
10
|
require 'fopost/http/response'
|
|
10
11
|
require 'fopost/http/transport'
|
|
11
12
|
require 'fopost/http/net_http_transport'
|
|
12
13
|
require 'fopost/http/client'
|
|
13
14
|
require 'fopost/client'
|
|
15
|
+
require 'fopost/webhooks'
|
|
14
16
|
|
|
15
17
|
# Official Ruby SDK for the FoPost API.
|
|
16
18
|
#
|
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: fopost
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.
|
|
4
|
+
version: 0.3.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- FoPost
|
|
@@ -22,21 +22,39 @@ files:
|
|
|
22
22
|
- lib/fopost.rb
|
|
23
23
|
- lib/fopost/client.rb
|
|
24
24
|
- lib/fopost/errors.rb
|
|
25
|
+
- lib/fopost/file_part.rb
|
|
25
26
|
- lib/fopost/http/client.rb
|
|
27
|
+
- lib/fopost/http/multipart.rb
|
|
26
28
|
- lib/fopost/http/net_http_transport.rb
|
|
27
29
|
- lib/fopost/http/response.rb
|
|
28
30
|
- lib/fopost/http/transport.rb
|
|
29
31
|
- lib/fopost/model.rb
|
|
30
32
|
- lib/fopost/models.rb
|
|
31
33
|
- lib/fopost/platforms.rb
|
|
34
|
+
- lib/fopost/resources/account_groups.rb
|
|
32
35
|
- lib/fopost/resources/accounts.rb
|
|
36
|
+
- lib/fopost/resources/activity.rb
|
|
37
|
+
- lib/fopost/resources/ads.rb
|
|
33
38
|
- lib/fopost/resources/ai.rb
|
|
39
|
+
- lib/fopost/resources/analytics.rb
|
|
34
40
|
- lib/fopost/resources/base.rb
|
|
41
|
+
- lib/fopost/resources/blog.rb
|
|
42
|
+
- lib/fopost/resources/blogs.rb
|
|
43
|
+
- lib/fopost/resources/broadcasts.rb
|
|
44
|
+
- lib/fopost/resources/contacts.rb
|
|
45
|
+
- lib/fopost/resources/google_ads.rb
|
|
46
|
+
- lib/fopost/resources/google_business.rb
|
|
47
|
+
- lib/fopost/resources/inbox.rb
|
|
48
|
+
- lib/fopost/resources/knowledge.rb
|
|
35
49
|
- lib/fopost/resources/labels.rb
|
|
50
|
+
- lib/fopost/resources/media.rb
|
|
36
51
|
- lib/fopost/resources/posts.rb
|
|
52
|
+
- lib/fopost/resources/validate.rb
|
|
53
|
+
- lib/fopost/resources/whatsapp.rb
|
|
37
54
|
- lib/fopost/resources/workspaces.rb
|
|
38
55
|
- lib/fopost/unset.rb
|
|
39
56
|
- lib/fopost/version.rb
|
|
57
|
+
- lib/fopost/webhooks.rb
|
|
40
58
|
homepage: https://fopost.com
|
|
41
59
|
licenses:
|
|
42
60
|
- MIT
|