omnisocials 0.6.0 → 0.8.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/README.md +35 -6
- data/lib/omnisocials/resources/inbox.rb +57 -24
- data/lib/omnisocials/resources/media.rb +11 -7
- data/lib/omnisocials/resources/posts.rb +16 -0
- data/lib/omnisocials/resources/webhooks.rb +4 -3
- data/lib/omnisocials/version.rb +1 -1
- metadata +2 -2
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 43d0cc71e753d98ddbaa8b1732f140ecfc3d578d02a8e8df81e248c6c9c75131
|
|
4
|
+
data.tar.gz: a66ae890324f9adc7b37b57add4e76ce1be1b110d4b15f751c611bcbfa6adf61
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 0b30af0ebca987fe7bebb4710d7648d2c0361e8709cdf716897d4f1f82808dc916159742dcae7c9c0512863e01f68435d642a9341e8254d9cc11511459b496ce
|
|
7
|
+
data.tar.gz: e50bd7f05fe6a2e516ff9ea31bb28307af242c4118ca1a30583008b88591e3b2d9c080fb50447137133f036e21d6fed8721b90bd380cfb670dc3870868ac7710
|
data/README.md
CHANGED
|
@@ -208,6 +208,20 @@ client.posts.reject("123", comment: "Wrong CTA link, please fix.") # reject and
|
|
|
208
208
|
|
|
209
209
|
Only works on a post with `approval_status: "pending"` (`status: "in_approval"`). Both act on behalf of the user who owns the API key, who must be a listed approver for the workflow's CURRENT step — steps approve in order, so being an approver on a later step is not enough yet (raises a 403 `OmniSocials::PermissionDeniedError`). Approving the last step finalizes the post (`scheduled` or `posting`); rejecting stops the whole workflow immediately, not just the current step.
|
|
210
210
|
|
|
211
|
+
### Read the approval review
|
|
212
|
+
|
|
213
|
+
```ruby
|
|
214
|
+
review = client.posts.get_approval("123")["data"]
|
|
215
|
+
if review["status"] == "rejected" && review["rejection"]
|
|
216
|
+
puts "Rejected by #{review["rejection"]["by"]["name"]}: #{review["rejection"]["reason"]}"
|
|
217
|
+
end
|
|
218
|
+
review["steps"].each do |step|
|
|
219
|
+
puts "#{step["order"]} #{step["name"]} #{step["status"]}"
|
|
220
|
+
end
|
|
221
|
+
```
|
|
222
|
+
|
|
223
|
+
`get_approval` returns the review of a post that went through an approval workflow: `status` (`none`, `pending`, `approved`, `rejected`), the `workflow`, who requested it and when, `current_step` (the step the post waits on, `nil` when the review ended), every step with its approvers and their decisions, the `rejection` (`by`, `reason`, `at`, `step`; `nil` when nobody rejected) and the `comments` thread, oldest first. A post without an approval workflow returns `status: "none"` with empty `steps` and `comments`. Read-only; needs the `posts:read` scope.
|
|
224
|
+
|
|
211
225
|
## Media
|
|
212
226
|
|
|
213
227
|
### Upload from a URL (recommended, up to 1GB)
|
|
@@ -231,7 +245,7 @@ media = client.media.upload(file: "/path/to/image.jpg", name: "hero-shot")
|
|
|
231
245
|
|
|
232
246
|
`file` accepts a file path (String or Pathname), an IO (`File.open("...", "rb")`, StringIO), or raw bytes as a binary-encoded String (e.g. from `File.binread`).
|
|
233
247
|
|
|
234
|
-
Uploading a PDF splits it into image slides (max 20 pages) and returns `slides` plus a `media_ids` array. Pass all of `media_ids`, in order, to `posts.create` to publish the deck as a carousel (a native swipeable document
|
|
248
|
+
Uploading a PDF splits it into image slides (max 20 pages) and returns `slides` plus a `media_ids` array. Pass all of `media_ids`, in order, to `posts.create` to publish the deck as a carousel (on LinkedIn a native swipeable document made from the original PDF file, which is kept so text and links stay intact; an image carousel elsewhere). To keep the PDF as ONE library item instead of one item per page, pass `pdf_mode` = `"document"`: the response then has a single `data` item of type `document` (its page images in `pdf.pages`) and `media_ids` holds that one id, which expands into every page at post time.
|
|
235
249
|
|
|
236
250
|
Every upload response also includes a `compatibility` block listing any connected platforms that would reject the file.
|
|
237
251
|
|
|
@@ -383,7 +397,7 @@ conversations["data"].each do |conversation|
|
|
|
383
397
|
puts "#{conversation["platform"]}: #{conversation["preview"]}"
|
|
384
398
|
end
|
|
385
399
|
|
|
386
|
-
conversation_id = conversations["data"][0]["
|
|
400
|
+
conversation_id = conversations["data"][0]["conversation_id"]
|
|
387
401
|
messages = client.inbox.get_messages(conversation_id)
|
|
388
402
|
messages["data"].each do |message|
|
|
389
403
|
puts "#{message["direction"]}: #{message["text"]}" # direction is "incoming" or "outgoing"
|
|
@@ -401,9 +415,9 @@ client.inbox.hide(message_id, hide: false) # unhide
|
|
|
401
415
|
client.inbox.delete_message(message_id)
|
|
402
416
|
```
|
|
403
417
|
|
|
404
|
-
`platform` accepts `"instagram"`, `"facebook"`, `"linkedin"`, `"tiktok"`, `"youtube"`, `"x"`, or `"threads"`; `type` accepts `"dm"`, `"comment"`, or `"mention"`. Threads conversations are comments (replies people leave on your Threads posts) and mentions; there are no Threads DMs. Comments can be hidden on Facebook, Instagram, TikTok, YouTube, and Threads (Threads: incoming top-level replies only), and a hidden message keeps its place in the conversation with its `hidden` flag set; `hidden` is `true`/`false` on comments and `nil` on DMs. A comment/mention's `post` carries `url` (public link when the platform provides one) and `media_type` (the platform's own label) next to `id`, `caption`, and `thumbnail`. Threads inbox
|
|
418
|
+
`platform` accepts `"instagram"`, `"facebook"`, `"linkedin"`, `"tiktok"`, `"youtube"`, `"x"`, or `"threads"`; `type` accepts `"dm"`, `"comment"`, or `"mention"`. Threads conversations are comments (replies people leave on your Threads posts) and mentions; there are no Threads DMs. Comments can be hidden on Facebook, Instagram, TikTok, YouTube, and Threads (Threads: incoming top-level replies only), and a hidden message keeps its place in the conversation with its `hidden` flag set; `hidden` is `true`/`false` on comments and `nil` on DMs. A comment/mention's `post` carries `url` (public link when the platform provides one) and `media_type` (the platform's own label) next to `id`, `caption`, and `thumbnail`. The Threads inbox needs a Threads connection with the reply permission (a 401 `reauth_required` means reconnect Threads; an account connected before 2026-09-14 needs this once). TikTok and YouTube replies are comments only; TikTok replies are capped at 150 characters. Conversation ids are URL-encoded for you, so pass them exactly as returned - LinkedIn ids contain `":"` and `"()"` (e.g. `"linkedin_comment_urn:li:activity:123"`).
|
|
405
419
|
|
|
406
|
-
`next` hands out the next conversation that still needs a reply (the customer's latest DM with no reply after it, or an unreplied comment/mention that is not hidden), with the whole thread and the post it belongs to, so a reply can be drafted from one call. Replies typed in the native apps count as answers. Only unread items are served by default, so `mark_read` is the durable way to skip one; `exclude` skips conversation ids for the current session only. Pass `include_next: true` to `reply` to get the following item in the same response. `list_conversations(unanswered: true)` gives the same set as a plain list.
|
|
420
|
+
`next` hands out the next conversation that still needs a reply (the customer's latest DM with no reply after it, or an unreplied comment/mention that is not hidden), with the whole thread and the post it belongs to, so a reply can be drafted from one call. DMs that can still be answered come first, then Instagram/Facebook DMs whose 24-hour window has closed (`reply_window["open"]` is `false`: answer those from the native app or mark them read), then comments and mentions, oldest first. Replies typed in the native apps count as answers. Only unread items are served by default, so `mark_read` is the durable way to skip one; `exclude` skips conversation ids for the current session only. Always pass `message_id: message["id"]` to `reply` on comment threads: every comment on a post shares one conversation, and without it the reply goes under the newest comment on the post. Pass `include_next: true` to `reply` to get the following item in the same response. `list_conversations(unanswered: true)` gives the same set as a plain list.
|
|
407
421
|
|
|
408
422
|
```ruby
|
|
409
423
|
item = client.inbox.next(platform: "instagram")
|
|
@@ -411,7 +425,20 @@ while item["data"]
|
|
|
411
425
|
message = item["data"]["message"]
|
|
412
426
|
puts "#{item["remaining"]} left. #{message["sender"]["username"]}: #{message["text"]}"
|
|
413
427
|
|
|
414
|
-
|
|
428
|
+
unless item["data"]["reply_window"]["open"]
|
|
429
|
+
# An Instagram/Facebook DM past Meta's 24-hour window: reply would raise
|
|
430
|
+
# 422 outside_messaging_window. Answer it in the app, or skip it.
|
|
431
|
+
client.inbox.mark_read(message["conversation_id"])
|
|
432
|
+
item = client.inbox.next(platform: "instagram")
|
|
433
|
+
next
|
|
434
|
+
end
|
|
435
|
+
|
|
436
|
+
reply = client.inbox.reply(
|
|
437
|
+
message["conversation_id"],
|
|
438
|
+
text: "Thanks! DM sent.",
|
|
439
|
+
message_id: message["id"], # the comment being answered, not the newest one
|
|
440
|
+
include_next: true
|
|
441
|
+
)
|
|
415
442
|
item = { "data" => reply["next"], "remaining" => reply["remaining"] || 0 }
|
|
416
443
|
end
|
|
417
444
|
```
|
|
@@ -437,7 +464,7 @@ end
|
|
|
437
464
|
|
|
438
465
|
## Webhooks
|
|
439
466
|
|
|
440
|
-
Subscribe to `post.scheduled`, `post.published`, and `post.
|
|
467
|
+
Subscribe to `post.scheduled`, `post.published`, `post.failed`, `post.approved`, and `post.rejected` events. `post.approved` fires when the last step of a post's approval workflow is approved, `post.rejected` when an approver rejects the post (it will not publish). These two carry `data["approval"]` with `status`, `decided_by` (the approver's user id) and `reason` (`nil` on `post.approved`), and an empty `data["targets"]`.
|
|
441
468
|
|
|
442
469
|
```ruby
|
|
443
470
|
webhook = client.webhooks.create(
|
|
@@ -480,6 +507,8 @@ class OmnisocialsWebhooksController < ApplicationController
|
|
|
480
507
|
event["data"]["targets"].each do |target|
|
|
481
508
|
Rails.logger.info "#{target["platform"]} #{target["status"]} #{target["native_post_id"]}"
|
|
482
509
|
end
|
|
510
|
+
elsif event["type"] == "post.rejected"
|
|
511
|
+
Rails.logger.info "Rejected: #{event["data"]["post_id"]} #{event["data"]["approval"]["reason"]}"
|
|
483
512
|
end
|
|
484
513
|
|
|
485
514
|
head :ok
|
|
@@ -26,12 +26,14 @@ module OmniSocials
|
|
|
26
26
|
# "linkedin", "tiktok", "youtube", "x", "threads"), type ("dm", "comment",
|
|
27
27
|
# "mention"), unread (only conversations with unread messages),
|
|
28
28
|
# unanswered (only conversations that still need an answer: the
|
|
29
|
-
# customer's latest DM has no reply after it,
|
|
30
|
-
#
|
|
31
|
-
#
|
|
32
|
-
#
|
|
33
|
-
#
|
|
34
|
-
#
|
|
29
|
+
# customer's latest DM has no reply after it, Instagram/Facebook DMs
|
|
30
|
+
# past Meta's 24-hour messaging window included (they cannot be answered
|
|
31
|
+
# through the API, but the customer is still waiting), or a
|
|
32
|
+
# comment/mention that has not been replied to and is not hidden;
|
|
33
|
+
# replies typed in the native apps count as answers, and read state is
|
|
34
|
+
# ignored, so use `next` for a work queue), limit (1-100), and cursor
|
|
35
|
+
# (an opaque cursor from a previous response's
|
|
36
|
+
# pagination["next_cursor"]).
|
|
35
37
|
#
|
|
36
38
|
# Threads conversations are type "comment" (replies people leave on the
|
|
37
39
|
# user's Threads posts; conversation ids look like
|
|
@@ -91,17 +93,33 @@ module OmniSocials
|
|
|
91
93
|
# in the dashboard to resume; DMs that arrived while suspended are
|
|
92
94
|
# not recovered).
|
|
93
95
|
#
|
|
96
|
+
# On comment and mention threads, pass message_id: (the "id" of the
|
|
97
|
+
# comment being answered: message["id"] from `next`, or a message "id"
|
|
98
|
+
# from `get_messages`). Every comment on a post shares one
|
|
99
|
+
# conversation, so without it the reply is posted under the newest
|
|
100
|
+
# comment on the post, which may be a different person than the one you
|
|
101
|
+
# drafted for. Ignored for DMs. 404 "not_found" when it is not an
|
|
102
|
+
# incoming message of this conversation.
|
|
103
|
+
#
|
|
104
|
+
# Instagram and Facebook DMs can only be answered within 24 hours of
|
|
105
|
+
# the customer's last message (Meta policy). That is checked before the
|
|
106
|
+
# send: a closed window raises 422 "outside_messaging_window" and
|
|
107
|
+
# nothing is sent (`next` reports the same in "reply_window"). Answer
|
|
108
|
+
# such a DM from the Instagram or Facebook app (mirrored into the
|
|
109
|
+
# inbox) or mark the conversation read; do not retry.
|
|
110
|
+
#
|
|
94
111
|
# Pass include_next: true to also get "next" (the next conversation
|
|
95
112
|
# that needs an answer, the same object `next` returns under "data",
|
|
96
113
|
# using its default queue order and filters; nil when nothing is
|
|
97
114
|
# waiting) and "remaining" in the response. Saves the extra call when
|
|
98
115
|
# working through the inbox.
|
|
99
|
-
def reply(conversation_id, text: nil, attachment_url: nil, attachment_type: nil, include_next: nil)
|
|
116
|
+
def reply(conversation_id, text: nil, attachment_url: nil, attachment_type: nil, message_id: nil, include_next: nil)
|
|
100
117
|
body = Internal.drop_nil(
|
|
101
118
|
{
|
|
102
119
|
"text" => text,
|
|
103
120
|
"attachment_url" => attachment_url,
|
|
104
121
|
"attachment_type" => attachment_type,
|
|
122
|
+
"message_id" => message_id,
|
|
105
123
|
"include_next" => include_next
|
|
106
124
|
}
|
|
107
125
|
)
|
|
@@ -133,9 +151,15 @@ module OmniSocials
|
|
|
133
151
|
# "not_found" (message not in this workspace) or
|
|
134
152
|
# "account_not_connected", 429 "quota_exceeded" (YouTube's daily API
|
|
135
153
|
# quota is used up; retry after midnight Pacific), 502 "platform_error"
|
|
136
|
-
# (the platform rejected the call)
|
|
137
|
-
#
|
|
138
|
-
#
|
|
154
|
+
# (the platform rejected the call), 502 "hide_not_applied" (Instagram
|
|
155
|
+
# accepted the call but, read back, still reports the comment in its
|
|
156
|
+
# old state; this happens with comments Instagram shows under "Comments
|
|
157
|
+
# from Facebook" on a reel that is also shared to Facebook, which live
|
|
158
|
+
# on Facebook where Instagram's hide does not reach them; the inbox row
|
|
159
|
+
# is left unchanged, so hide it in the Instagram or Facebook app and do
|
|
160
|
+
# not retry). A Threads account connected before 2026-09-14 needs a
|
|
161
|
+
# one-time reconnect; until then a Threads hide answers 401
|
|
162
|
+
# "reauth_required".
|
|
139
163
|
def hide(message_id, hide: true)
|
|
140
164
|
@client.request(
|
|
141
165
|
"POST", "/inbox/messages/#{encode_id(message_id)}/hide",
|
|
@@ -165,13 +189,17 @@ module OmniSocials
|
|
|
165
189
|
end
|
|
166
190
|
|
|
167
191
|
# GET /inbox/next - the next conversation that needs an answer: a work
|
|
168
|
-
# queue for answering the inbox. Returns
|
|
169
|
-
#
|
|
170
|
-
#
|
|
171
|
-
#
|
|
172
|
-
# reply after it (Instagram/Facebook DMs within the 24-hour messaging
|
|
173
|
-
# window only, since Meta refuses replies outside it), or a
|
|
192
|
+
# queue for answering the inbox. Returns one item that still needs a
|
|
193
|
+
# reply, together with its conversation so far and the post it belongs
|
|
194
|
+
# to, so a reply can be drafted from one call. An item needs an answer
|
|
195
|
+
# when it is the customer's latest DM with no reply after it, or a
|
|
174
196
|
# comment/mention that has not been replied to and is not hidden.
|
|
197
|
+
# Order: DMs that can still be answered come first (Instagram/Facebook
|
|
198
|
+
# DMs inside Meta's 24-hour window, the one whose window closes soonest
|
|
199
|
+
# first, and X DMs), then Instagram/Facebook DMs whose window has
|
|
200
|
+
# closed (served with "reply_window"["open"] false: answer them from
|
|
201
|
+
# the native app or mark them read), then comments and mentions, oldest
|
|
202
|
+
# first by default.
|
|
175
203
|
# Replies typed in the native apps count as answers (they are mirrored
|
|
176
204
|
# into the inbox), so a thread a colleague answered on their phone is
|
|
177
205
|
# not served again. Instagram mentions are skipped (no reply path).
|
|
@@ -182,16 +210,21 @@ module OmniSocials
|
|
|
182
210
|
# include read-but-unanswered items. exclude is a session-local skip:
|
|
183
211
|
# conversation ids (an Array, or a comma-separated String) to leave out
|
|
184
212
|
# of this call, up to 100. order is "oldest" (default: the item that
|
|
185
|
-
# has waited longest first) or "newest"
|
|
186
|
-
# "comment", "mention") narrow the
|
|
213
|
+
# has waited longest first) or "newest"; it reverses the order within
|
|
214
|
+
# each group. platform and type ("dm", "comment", "mention") narrow the
|
|
215
|
+
# queue.
|
|
187
216
|
#
|
|
188
217
|
# Returns { "data" => ..., "remaining" => Integer }. "data" is
|
|
189
|
-
# { "conversation", "message", "messages" }, or nil
|
|
190
|
-
# waiting. "message" is the unanswered incoming item
|
|
191
|
-
# customer's latest DM, or the specific comment): its "id"
|
|
192
|
-
# `hide` and `delete_message` take
|
|
193
|
-
# `reply`
|
|
194
|
-
#
|
|
218
|
+
# { "conversation", "message", "messages", "reply_window" }, or nil
|
|
219
|
+
# when nothing is waiting. "message" is the unanswered incoming item
|
|
220
|
+
# itself (the customer's latest DM, or the specific comment): its "id"
|
|
221
|
+
# is what `hide` and `delete_message` take and the message_id: to pass
|
|
222
|
+
# to `reply` on comment threads, its "conversation_id" is what `reply`
|
|
223
|
+
# takes. "messages" is the conversation so far, oldest first (the most
|
|
224
|
+
# recent 50 messages for long DM threads). "reply_window" is
|
|
225
|
+
# { "open" => Boolean, "closes_at" => String|nil }: "open" is false
|
|
226
|
+
# only for an Instagram/Facebook DM past its 24-hour window, which
|
|
227
|
+
# `reply` refuses with 422 "outside_messaging_window". "remaining" is the
|
|
195
228
|
# number of unanswered items still waiting after this one (capped at
|
|
196
229
|
# 500), 0 when "data" is nil. To chain the queue, pass
|
|
197
230
|
# include_next: true to `reply` and it returns the next item in the
|
|
@@ -32,13 +32,15 @@ module OmniSocials
|
|
|
32
32
|
# binary-encoded String, e.g. from File.binread).
|
|
33
33
|
#
|
|
34
34
|
# A PDF is split into image slides and the response carries `slides`
|
|
35
|
-
# plus a `media_ids` array instead of a single `data` item
|
|
35
|
+
# plus a `media_ids` array instead of a single `data` item; pass
|
|
36
|
+
# pdf_mode: "document" to keep it as ONE item of type "document" whose
|
|
37
|
+
# single id in media_ids expands into every page. For files
|
|
36
38
|
# over 100MB use upload_from_url (up to 1GB) or the create_upload_url
|
|
37
39
|
# presigned flow.
|
|
38
|
-
def upload(file:, filename: nil, name: nil, folder: nil, folder_id: nil)
|
|
40
|
+
def upload(file:, filename: nil, name: nil, folder: nil, folder_id: nil, pdf_mode: nil)
|
|
39
41
|
upload_name, data = Internal.coerce_file(file, filename)
|
|
40
42
|
fields = Internal.drop_nil(
|
|
41
|
-
{ "name" => name, "folder" => folder, "folder_id" => folder_id }
|
|
43
|
+
{ "name" => name, "folder" => folder, "folder_id" => folder_id, "pdf_mode" => pdf_mode }
|
|
42
44
|
)
|
|
43
45
|
@client.request(
|
|
44
46
|
"POST", "/media/upload",
|
|
@@ -50,14 +52,15 @@ module OmniSocials
|
|
|
50
52
|
#
|
|
51
53
|
# Files over 100MB are streamed in the background: the response has
|
|
52
54
|
# data["status"] == "processing"; poll get() until it is "ready".
|
|
53
|
-
def upload_from_url(url:, filename: nil, name: nil, folder: nil, folder_id: nil)
|
|
55
|
+
def upload_from_url(url:, filename: nil, name: nil, folder: nil, folder_id: nil, pdf_mode: nil)
|
|
54
56
|
body = Internal.drop_nil(
|
|
55
57
|
{
|
|
56
58
|
"url" => url,
|
|
57
59
|
"filename" => filename,
|
|
58
60
|
"name" => name,
|
|
59
61
|
"folder" => folder,
|
|
60
|
-
"folder_id" => folder_id
|
|
62
|
+
"folder_id" => folder_id,
|
|
63
|
+
"pdf_mode" => pdf_mode
|
|
61
64
|
}
|
|
62
65
|
)
|
|
63
66
|
@client.request("POST", "/media/upload-from-url", json: body)
|
|
@@ -66,7 +69,7 @@ module OmniSocials
|
|
|
66
69
|
# POST /media/upload-from-base64 - upload base64-encoded data (without
|
|
67
70
|
# a data URI prefix).
|
|
68
71
|
def upload_from_base64(data:, mime_type:, filename: nil, name: nil,
|
|
69
|
-
folder: nil, folder_id: nil)
|
|
72
|
+
folder: nil, folder_id: nil, pdf_mode: nil)
|
|
70
73
|
body = Internal.drop_nil(
|
|
71
74
|
{
|
|
72
75
|
"data" => data,
|
|
@@ -74,7 +77,8 @@ module OmniSocials
|
|
|
74
77
|
"filename" => filename,
|
|
75
78
|
"name" => name,
|
|
76
79
|
"folder" => folder,
|
|
77
|
-
"folder_id" => folder_id
|
|
80
|
+
"folder_id" => folder_id,
|
|
81
|
+
"pdf_mode" => pdf_mode
|
|
78
82
|
}
|
|
79
83
|
)
|
|
80
84
|
@client.request("POST", "/media/upload-from-base64", json: body)
|
|
@@ -238,6 +238,22 @@ module OmniSocials
|
|
|
238
238
|
@client.request("POST", "/posts/#{post_id}/reject", json: body)
|
|
239
239
|
end
|
|
240
240
|
|
|
241
|
+
# GET /posts/{id}/approval - the approval review of a post: every step
|
|
242
|
+
# with its approvers and their decisions, the rejection with its
|
|
243
|
+
# reason, and the comment thread. Use it when approval_status is
|
|
244
|
+
# "rejected" to learn who rejected the post and why, or while it is
|
|
245
|
+
# "pending" to see who the post waits for.
|
|
246
|
+
#
|
|
247
|
+
# `data` carries post_id, status ("none", "pending", "approved",
|
|
248
|
+
# "rejected"), workflow, requested_by, requested_at, current_step,
|
|
249
|
+
# steps, rejection and comments (oldest first). A post without an
|
|
250
|
+
# approval workflow returns status "none" with the object fields nil
|
|
251
|
+
# and empty steps and comments. Read-only; requires the posts:read
|
|
252
|
+
# scope.
|
|
253
|
+
def get_approval(post_id)
|
|
254
|
+
@client.request("GET", "/posts/#{post_id}/approval")
|
|
255
|
+
end
|
|
256
|
+
|
|
241
257
|
private
|
|
242
258
|
|
|
243
259
|
def create_body(content:, channels:, scheduled_at:, media_ids:,
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
module OmniSocials
|
|
4
4
|
module Resources
|
|
5
5
|
# Webhooks resource: manage event subscriptions (post.scheduled,
|
|
6
|
-
# post.published, post.failed).
|
|
6
|
+
# post.published, post.failed, post.approved, post.rejected).
|
|
7
7
|
#
|
|
8
8
|
# For verifying incoming deliveries, see OmniSocials::Webhooks.verify.
|
|
9
9
|
class Webhooks
|
|
@@ -24,8 +24,9 @@ module OmniSocials
|
|
|
24
24
|
# POST /webhooks - create a webhook subscription.
|
|
25
25
|
#
|
|
26
26
|
# `url` must be HTTPS. `events` is a non-empty subset of
|
|
27
|
-
# post.scheduled, post.published, post.failed.
|
|
28
|
-
# only returned once, in this
|
|
27
|
+
# post.scheduled, post.published, post.failed, post.approved,
|
|
28
|
+
# post.rejected. The signing `secret` is only returned once, in this
|
|
29
|
+
# response - store it.
|
|
29
30
|
def create(url:, events:)
|
|
30
31
|
@client.request(
|
|
31
32
|
"POST", "/webhooks",
|
data/lib/omnisocials/version.rb
CHANGED
metadata
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: omnisocials
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.
|
|
4
|
+
version: 0.8.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- OmniSocials
|
|
8
8
|
autorequire:
|
|
9
9
|
bindir: bin
|
|
10
10
|
cert_chain: []
|
|
11
|
-
date: 2026-
|
|
11
|
+
date: 2026-10-05 00:00:00.000000000 Z
|
|
12
12
|
dependencies: []
|
|
13
13
|
description: Schedule and publish social media posts, upload media, and read analytics
|
|
14
14
|
across Instagram, Facebook, LinkedIn, YouTube, TikTok, X, Pinterest, Bluesky, Threads,
|