omnisocials 0.4.0 → 0.5.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 +53 -5
- data/lib/omnisocials/resources/inbox.rb +46 -10
- data/lib/omnisocials/resources/locations.rb +37 -5
- data/lib/omnisocials/resources/posts.rb +51 -10
- 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: 2dff156c383d34ece380b804eb9dc42fd00bd42782218bd99e7f840dfc4c197e
|
|
4
|
+
data.tar.gz: 8a7d23f25f08551a42e4b5ad30dc680f2b83045f72bb78c894a9ab13134df8e1
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 3f3ca94edd9dbac14a91c91fc17c39b22efda2385dc36fd6fbc02b881c2f86a661e672a55665e97c47d4a8ee278ed6cb0e9bd961f4dcba495b936e6840316b88
|
|
7
|
+
data.tar.gz: 9f99feca0fef8165c5d80901cc60f4a3e8f681025e8c786e2e32938582e4194c9a98c8ba60ca54d1166e603bbc3c1f1e751dbdc580e42e9a3032bd650760511a
|
data/README.md
CHANGED
|
@@ -103,9 +103,9 @@ post = client.posts.create(
|
|
|
103
103
|
|
|
104
104
|
Note that `linkedin` targets a personal LinkedIn profile and `linkedin_page` targets a LinkedIn company page. Both can be connected to the same workspace and posted to independently.
|
|
105
105
|
|
|
106
|
-
### X
|
|
106
|
+
### Chained threads (X, Bluesky, Mastodon, Threads)
|
|
107
107
|
|
|
108
|
-
Pass 2 to 25 `thread_parts` to publish a chained thread instead of a single tweet (each part is at most 280 characters). Bluesky and
|
|
108
|
+
Pass 2 to 25 `thread_parts` to publish a chained thread instead of a single tweet (each part is at most 280 characters). Bluesky, Mastodon and Threads support the same `thread_parts` shape (Threads: 2 to 25 parts, 500 characters per part, up to 10 media per part; parts after the first publish as replies to the previous part, and the Threads caption is taken from part 1):
|
|
109
109
|
|
|
110
110
|
```ruby
|
|
111
111
|
post = client.posts.create(
|
|
@@ -123,7 +123,24 @@ post = client.posts.create(
|
|
|
123
123
|
)
|
|
124
124
|
```
|
|
125
125
|
|
|
126
|
-
|
|
126
|
+
```ruby
|
|
127
|
+
# Meta Threads chain with a carousel on the first part
|
|
128
|
+
post = client.posts.create(
|
|
129
|
+
content: "Behind the scenes of our summer shoot",
|
|
130
|
+
channels: ["threads"],
|
|
131
|
+
threads: {
|
|
132
|
+
"thread_parts" => [
|
|
133
|
+
{ "text" => "Behind the scenes of our summer shoot. A few highlights:", "media_urls" => ["https://example.com/shoot-1.jpg", "https://example.com/shoot-2.jpg"] },
|
|
134
|
+
{ "text" => "Day one: scouting locations at sunrise." },
|
|
135
|
+
{ "text" => "Day two: the full crew, 14 hours, zero regrets." }
|
|
136
|
+
]
|
|
137
|
+
}
|
|
138
|
+
)
|
|
139
|
+
```
|
|
140
|
+
|
|
141
|
+
On update, passing `x: { "thread_parts" => nil }` clears the thread and reverts the post to single-tweet mode (same for `bluesky`, `mastodon` and `threads`). Only top-level `nil` values are dropped from request bodies, so nested `nil` values like this one are sent as JSON `null`.
|
|
142
|
+
|
|
143
|
+
Threads posts can also carry a location tag: pass `threads: { "location_id" => "..." }` with an id from `client.locations.search(platform: "threads")` (see Locations below). On a multi-post thread the tag is applied to part 1, and on update `threads: { "location_id" => nil }` clears it. Threads location tagging is currently rolling out; until Meta approves the permissions it is disabled on production and calls return a clear error.
|
|
127
144
|
|
|
128
145
|
### X link posts use credits
|
|
129
146
|
|
|
@@ -182,6 +199,15 @@ recent = client.posts.recent_platform(limit: 10, platforms: ["instagram", "tikto
|
|
|
182
199
|
|
|
183
200
|
`retry` re-publishes only the platforms that failed, on the same post; platforms that already succeeded are never posted again. It is asynchronous: a 200 means the retry is queued, so poll `get` for the outcome. Max 3 retries per platform.
|
|
184
201
|
|
|
202
|
+
### Approve or reject a post
|
|
203
|
+
|
|
204
|
+
```ruby
|
|
205
|
+
client.posts.approve("123") # approve the current approval-workflow step
|
|
206
|
+
client.posts.reject("123", comment: "Wrong CTA link, please fix.") # reject and stop the workflow (comment optional)
|
|
207
|
+
```
|
|
208
|
+
|
|
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
|
+
|
|
185
211
|
## Media
|
|
186
212
|
|
|
187
213
|
### Upload from a URL (recommended, up to 1GB)
|
|
@@ -315,7 +341,7 @@ best = client.analytics.best_times(platform: "instagram", timezone: "Europe/Amst
|
|
|
315
341
|
best["data"]["best_times"].each { |slot| puts slot }
|
|
316
342
|
```
|
|
317
343
|
|
|
318
|
-
## Locations (Instagram place tagging)
|
|
344
|
+
## Locations (Instagram and Threads place tagging)
|
|
319
345
|
|
|
320
346
|
```ruby
|
|
321
347
|
results = client.locations.search("Blue Bottle Coffee Oakland")
|
|
@@ -330,6 +356,23 @@ client.posts.create(
|
|
|
330
356
|
)
|
|
331
357
|
```
|
|
332
358
|
|
|
359
|
+
Threads uses its own location ids (a Facebook Place ID is not a Threads location id). Pass `platform: "threads"` and search by keyword, or by `latitude` plus `longitude` instead of `q`; use a result's `id` as `threads.location_id` on a post:
|
|
360
|
+
|
|
361
|
+
```ruby
|
|
362
|
+
results = client.locations.search("Blue Bottle Coffee Oakland", platform: "threads")
|
|
363
|
+
# or around a point instead of a keyword:
|
|
364
|
+
results = client.locations.search(platform: "threads", latitude: 37.8044, longitude: -122.2712)
|
|
365
|
+
threads_location_id = results["locations"][0]["id"]
|
|
366
|
+
|
|
367
|
+
client.posts.create(
|
|
368
|
+
content: "Great coffee here",
|
|
369
|
+
channels: ["threads"],
|
|
370
|
+
threads: { "location_id" => threads_location_id }
|
|
371
|
+
)
|
|
372
|
+
```
|
|
373
|
+
|
|
374
|
+
The Threads response is `{ "locations" => [...] }` (each with nullable `name`, `address`, `city`, `country`, `latitude`, `longitude`) or `{ "error" => { "code", "message" } }` with `code` one of `not_available`, `threads_not_connected`, `threads_reauth_required` (reconnect Threads), or `platform_error`. Threads location tagging is currently rolling out; until Meta approves the permissions it is disabled on production and calls return a clear error.
|
|
375
|
+
|
|
333
376
|
## Inbox
|
|
334
377
|
|
|
335
378
|
Read and reply to social inbox conversations (DMs, comments, mentions) across connected platforms. Requires an API key with the opt-in `inbox:read` / `inbox:write` scopes. The list endpoints are cursor-paginated (unlike the offset pagination used elsewhere): page while `pagination["has_more"]` is true by passing the previous response's `pagination["next_cursor"]` as `cursor`.
|
|
@@ -348,9 +391,14 @@ end
|
|
|
348
391
|
|
|
349
392
|
client.inbox.mark_read(conversation_id)
|
|
350
393
|
client.inbox.reply(conversation_id, text: "Thanks for reaching out!")
|
|
394
|
+
|
|
395
|
+
# Threads only: hide or unhide a reply someone left on one of your Threads posts.
|
|
396
|
+
message_id = messages["data"][0]["id"]
|
|
397
|
+
client.inbox.hide(message_id) # hide
|
|
398
|
+
client.inbox.hide(message_id, hide: false) # unhide
|
|
351
399
|
```
|
|
352
400
|
|
|
353
|
-
`platform` accepts `"instagram"`, `"facebook"`, `"linkedin"`, `"tiktok"`,
|
|
401
|
+
`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. Only incoming top-level Threads replies can be hidden (nested replies cannot), and a hidden message keeps its place in the conversation with its `hidden` flag set. Threads inbox is currently rolling out; until Meta approves the permissions it is disabled on production and calls return a clear error, and it needs a Threads connection with the reply permission (a 401 `reauth_required` means reconnect Threads). 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"`).
|
|
354
402
|
|
|
355
403
|
Replying to an X DM costs 2 prepaid credits, debited from the company balance before the send and automatically refunded if the send fails:
|
|
356
404
|
|
|
@@ -23,10 +23,18 @@ module OmniSocials
|
|
|
23
23
|
# GET /inbox/conversations - list conversations, newest activity first.
|
|
24
24
|
#
|
|
25
25
|
# All filters are optional: platform ("instagram", "facebook",
|
|
26
|
-
# "linkedin", "tiktok", "x"), type ("dm", "comment",
|
|
27
|
-
# unread (only conversations with unread messages), limit
|
|
28
|
-
# cursor (an opaque cursor from a previous response's
|
|
26
|
+
# "linkedin", "tiktok", "youtube", "x", "threads"), type ("dm", "comment",
|
|
27
|
+
# "mention"), unread (only conversations with unread messages), limit
|
|
28
|
+
# (1-100), and cursor (an opaque cursor from a previous response's
|
|
29
29
|
# pagination["next_cursor"]).
|
|
30
|
+
#
|
|
31
|
+
# Threads conversations are type "comment" (replies people leave on the
|
|
32
|
+
# user's Threads posts; conversation ids look like
|
|
33
|
+
# "threads_comment_<rootPostId>") and "mention"
|
|
34
|
+
# ("threads_mention_<postId>"); there are no Threads DMs. Threads inbox
|
|
35
|
+
# is currently rolling out: until Meta approves the permissions it is
|
|
36
|
+
# disabled on production and calls return a clear error, and it needs a
|
|
37
|
+
# Threads connection with the reply permission.
|
|
30
38
|
def list_conversations(platform: nil, type: nil, unread: nil, limit: nil, cursor: nil)
|
|
31
39
|
@client.request(
|
|
32
40
|
"GET", "/inbox/conversations",
|
|
@@ -58,9 +66,17 @@ module OmniSocials
|
|
|
58
66
|
# POST /inbox/conversations/{id}/reply - send a reply into the
|
|
59
67
|
# conversation (a DM message, or a reply to the comment/mention).
|
|
60
68
|
#
|
|
61
|
-
#
|
|
62
|
-
# URL with `attachment_url` plus `attachment_type` ("image",
|
|
63
|
-
# "audio", or "file")
|
|
69
|
+
# On Facebook and Instagram DMs, optionally attach a single media asset
|
|
70
|
+
# by public URL with `attachment_url` plus `attachment_type` ("image",
|
|
71
|
+
# "video", "audio", or "file"); `text` is optional when `attachment_url`
|
|
72
|
+
# is set (an attachment-only reply is allowed). Other platforms are
|
|
73
|
+
# text-only. Returns the created outgoing message.
|
|
74
|
+
#
|
|
75
|
+
# On a Threads conversation the reply publishes as a native Threads
|
|
76
|
+
# reply. Threads inbox is currently rolling out (disabled on production
|
|
77
|
+
# until Meta App Review) and needs a Threads connection with the reply
|
|
78
|
+
# permission: a 401 with code "reauth_required" means the connection
|
|
79
|
+
# lacks that permission (reconnect Threads).
|
|
64
80
|
#
|
|
65
81
|
# Replying to an X DM costs 2 prepaid credits, debited from the
|
|
66
82
|
# company balance before the send and automatically refunded if the
|
|
@@ -70,7 +86,7 @@ module OmniSocials
|
|
|
70
86
|
# auto-suspended after the balance hit zero (top up and re-enable it
|
|
71
87
|
# in the dashboard to resume; DMs that arrived while suspended are
|
|
72
88
|
# not recovered).
|
|
73
|
-
def reply(conversation_id, text
|
|
89
|
+
def reply(conversation_id, text: nil, attachment_url: nil, attachment_type: nil)
|
|
74
90
|
body = Internal.drop_nil(
|
|
75
91
|
{
|
|
76
92
|
"text" => text,
|
|
@@ -84,11 +100,31 @@ module OmniSocials
|
|
|
84
100
|
)
|
|
85
101
|
end
|
|
86
102
|
|
|
103
|
+
# POST /inbox/messages/{id}/hide - hide or unhide a reply someone left
|
|
104
|
+
# on one of the user's Threads posts, as the post owner (Threads only
|
|
105
|
+
# for now). Pass hide: false to unhide. Only incoming top-level replies
|
|
106
|
+
# can be hidden (Threads does not allow hiding nested replies); the
|
|
107
|
+
# message keeps its place in the conversation. Returns the updated
|
|
108
|
+
# message with its "hidden" flag flipped.
|
|
109
|
+
#
|
|
110
|
+
# Errors: 400 "unsupported_platform" (not an incoming Threads reply, or
|
|
111
|
+
# Threads inbox not available yet), 400 "not_hideable" (nested reply or
|
|
112
|
+
# Threads refused), 401 "reauth_required" (connection lacks the reply
|
|
113
|
+
# permission; reconnect Threads), 404 "not_found" (message not in this
|
|
114
|
+
# workspace) or "account_not_connected" (no Threads account).
|
|
115
|
+
def hide(message_id, hide: true)
|
|
116
|
+
@client.request(
|
|
117
|
+
"POST", "/inbox/messages/#{encode_id(message_id)}/hide",
|
|
118
|
+
json: { "hide" => hide }
|
|
119
|
+
)
|
|
120
|
+
end
|
|
121
|
+
|
|
87
122
|
private
|
|
88
123
|
|
|
89
|
-
# URL-encode a conversation id for use in a path segment.
|
|
90
|
-
# contain ":" and "()", so they must be
|
|
91
|
-
# (a path segment treats "+" literally,
|
|
124
|
+
# URL-encode a conversation or message id for use in a path segment.
|
|
125
|
+
# LinkedIn conversation ids contain ":" and "()", so they must be
|
|
126
|
+
# escaped; spaces become %20 (a path segment treats "+" literally,
|
|
127
|
+
# unlike a query string).
|
|
92
128
|
def encode_id(conversation_id)
|
|
93
129
|
CGI.escape(conversation_id.to_s).gsub("+", "%20")
|
|
94
130
|
end
|
|
@@ -2,16 +2,48 @@
|
|
|
2
2
|
|
|
3
3
|
module OmniSocials
|
|
4
4
|
module Resources
|
|
5
|
-
# Locations resource: Instagram place tagging (search +
|
|
5
|
+
# Locations resource: Instagram and Threads place tagging (search +
|
|
6
|
+
# validate).
|
|
6
7
|
class Locations
|
|
7
8
|
def initialize(client)
|
|
8
9
|
@client = client
|
|
9
10
|
end
|
|
10
11
|
|
|
11
|
-
# GET /locations/search
|
|
12
|
-
#
|
|
13
|
-
|
|
14
|
-
|
|
12
|
+
# GET /locations/search - search locations for place tagging.
|
|
13
|
+
#
|
|
14
|
+
# platform is "instagram" (default) or "threads". The two sources use
|
|
15
|
+
# DIFFERENT ids: a Facebook Place ID is not a Threads location id.
|
|
16
|
+
#
|
|
17
|
+
# Instagram: pass q to search Facebook place pages usable as an
|
|
18
|
+
# Instagram location_id. Response: { "data" => [...] } plus optional
|
|
19
|
+
# "error" (a plain string on the degraded path) and "needsPermission".
|
|
20
|
+
#
|
|
21
|
+
# Threads: pass q, OR latitude (-90..90) plus longitude (-180..180) to
|
|
22
|
+
# search around a point instead of q. Response:
|
|
23
|
+
# { "locations" => [{ id, name, address, city, country, latitude,
|
|
24
|
+
# longitude }] } (all fields but id nullable), or
|
|
25
|
+
# { "error" => { "code", "message" } } where code is one of
|
|
26
|
+
# "not_available" (Threads location tagging not enabled in this
|
|
27
|
+
# environment yet), "threads_not_connected", "threads_reauth_required"
|
|
28
|
+
# (the connection lacks the threads_location_tagging permission;
|
|
29
|
+
# reconnect Threads), or "platform_error". Validation problems (neither
|
|
30
|
+
# q nor lat+lng, q under 2 chars, coordinates out of range) raise a 400
|
|
31
|
+
# with the standard error envelope. Pass a result's id as
|
|
32
|
+
# threads.location_id on post create/update.
|
|
33
|
+
#
|
|
34
|
+
# Threads location tagging is currently rolling out: until Meta
|
|
35
|
+
# approves the permissions it is disabled on production and calls
|
|
36
|
+
# return a clear error.
|
|
37
|
+
def search(q = nil, platform: nil, latitude: nil, longitude: nil)
|
|
38
|
+
@client.request(
|
|
39
|
+
"GET", "/locations/search",
|
|
40
|
+
query: {
|
|
41
|
+
"q" => q,
|
|
42
|
+
"platform" => platform,
|
|
43
|
+
"latitude" => latitude,
|
|
44
|
+
"longitude" => longitude
|
|
45
|
+
}
|
|
46
|
+
)
|
|
15
47
|
end
|
|
16
48
|
|
|
17
49
|
# GET /locations/validate?id= - validate a location id before attaching
|
|
@@ -11,8 +11,8 @@ module OmniSocials
|
|
|
11
11
|
# for media_urls, `{ "id" => "...", "alt" => "..." }` for media_ids. Alt
|
|
12
12
|
# text is delivered to Mastodon (media description), Bluesky (embed alt),
|
|
13
13
|
# X (photos/GIFs), Pinterest (pin alt text), Instagram (images), and
|
|
14
|
-
# LinkedIn (images); the same entry shape works inside
|
|
15
|
-
# `thread_parts` media.
|
|
14
|
+
# LinkedIn (images); the same entry shape works inside
|
|
15
|
+
# x/bluesky/mastodon/threads `thread_parts` media.
|
|
16
16
|
class Posts
|
|
17
17
|
def initialize(client)
|
|
18
18
|
@client = client
|
|
@@ -67,6 +67,18 @@ module OmniSocials
|
|
|
67
67
|
# company's total reserved credits past its balance. Drafts are never
|
|
68
68
|
# gated, and posts scheduled to publish before 2026-08-14 are never
|
|
69
69
|
# gated either.
|
|
70
|
+
#
|
|
71
|
+
# Threads posts can carry a location tag: pass
|
|
72
|
+
# threads: { "location_id" => "..." } (an id from locations.search with
|
|
73
|
+
# platform: "threads"), or threads: { "location" => { "id" => "...",
|
|
74
|
+
# "name" => "..." } } to store display fields along with the id
|
|
75
|
+
# (location_id wins when both are given). On a multi-post thread
|
|
76
|
+
# (thread_parts) the tag is applied to part 1, and the Post's "threads"
|
|
77
|
+
# block echoes a "location" object when set. Threads location tagging
|
|
78
|
+
# is currently rolling out: until Meta approves the permissions it is
|
|
79
|
+
# disabled on production and create/update/publish return a 400 (also a
|
|
80
|
+
# 400 validation_error asking you to reconnect Threads when the
|
|
81
|
+
# connection lacks the threads_location_tagging permission).
|
|
70
82
|
def create(content:, channels: nil, scheduled_at: nil, media_ids: nil,
|
|
71
83
|
media_urls: nil, type: nil, source: nil, link_url: nil,
|
|
72
84
|
link_title: nil, link_description: nil, link_thumbnail_url: nil,
|
|
@@ -75,7 +87,8 @@ module OmniSocials
|
|
|
75
87
|
hashtag_platforms: nil, pinterest: nil, youtube: nil,
|
|
76
88
|
instagram: nil, facebook: nil, linkedin: nil,
|
|
77
89
|
linkedin_page: nil, tiktok: nil, x: nil, bluesky: nil,
|
|
78
|
-
mastodon: nil,
|
|
90
|
+
mastodon: nil, threads: nil, google_business: nil,
|
|
91
|
+
linkedin_poll: nil)
|
|
79
92
|
body = create_body(
|
|
80
93
|
content: content, channels: channels, scheduled_at: scheduled_at,
|
|
81
94
|
media_ids: media_ids, media_urls: media_urls, type: type,
|
|
@@ -87,7 +100,7 @@ module OmniSocials
|
|
|
87
100
|
hashtag_platforms: hashtag_platforms, pinterest: pinterest,
|
|
88
101
|
youtube: youtube, instagram: instagram, facebook: facebook,
|
|
89
102
|
linkedin: linkedin, linkedin_page: linkedin_page, tiktok: tiktok,
|
|
90
|
-
x: x, bluesky: bluesky, mastodon: mastodon,
|
|
103
|
+
x: x, bluesky: bluesky, mastodon: mastodon, threads: threads,
|
|
91
104
|
google_business: google_business, linkedin_poll: linkedin_poll
|
|
92
105
|
)
|
|
93
106
|
@client.request("POST", "/posts/create", json: body)
|
|
@@ -106,7 +119,8 @@ module OmniSocials
|
|
|
106
119
|
pinterest: nil, youtube: nil, instagram: nil,
|
|
107
120
|
facebook: nil, linkedin: nil, linkedin_page: nil,
|
|
108
121
|
tiktok: nil, x: nil, bluesky: nil, mastodon: nil,
|
|
109
|
-
|
|
122
|
+
threads: nil, google_business: nil,
|
|
123
|
+
linkedin_poll: nil)
|
|
110
124
|
body = create_body(
|
|
111
125
|
content: content, channels: channels, scheduled_at: nil,
|
|
112
126
|
media_ids: media_ids, media_urls: media_urls, type: type,
|
|
@@ -118,7 +132,7 @@ module OmniSocials
|
|
|
118
132
|
hashtag_platforms: hashtag_platforms, pinterest: pinterest,
|
|
119
133
|
youtube: youtube, instagram: instagram, facebook: facebook,
|
|
120
134
|
linkedin: linkedin, linkedin_page: linkedin_page, tiktok: tiktok,
|
|
121
|
-
x: x, bluesky: bluesky, mastodon: mastodon,
|
|
135
|
+
x: x, bluesky: bluesky, mastodon: mastodon, threads: threads,
|
|
122
136
|
google_business: google_business, linkedin_poll: linkedin_poll
|
|
123
137
|
)
|
|
124
138
|
@client.request("POST", "/posts/create-and-publish", json: body)
|
|
@@ -128,8 +142,9 @@ module OmniSocials
|
|
|
128
142
|
#
|
|
129
143
|
# Only top-level nils are dropped from the body, so passing e.g.
|
|
130
144
|
# x: { "thread_parts" => nil } still clears an X thread (reverts the
|
|
131
|
-
# post to single-tweet mode). The same applies to bluesky
|
|
132
|
-
# thread parts
|
|
145
|
+
# post to single-tweet mode). The same applies to bluesky, mastodon
|
|
146
|
+
# and threads thread parts, and to a Threads location tag:
|
|
147
|
+
# threads: { "location_id" => nil } (or "location" => nil) clears it.
|
|
133
148
|
#
|
|
134
149
|
# See #create for the 402 "x_credits_insufficient" credit gate that
|
|
135
150
|
# can also refuse an update to a scheduled X link post.
|
|
@@ -138,7 +153,8 @@ module OmniSocials
|
|
|
138
153
|
collaborators: nil, user_tags: nil, pinterest: nil,
|
|
139
154
|
youtube: nil, instagram: nil, facebook: nil, linkedin: nil,
|
|
140
155
|
linkedin_page: nil, tiktok: nil, x: nil, bluesky: nil,
|
|
141
|
-
mastodon: nil,
|
|
156
|
+
mastodon: nil, threads: nil, google_business: nil,
|
|
157
|
+
linkedin_poll: nil)
|
|
142
158
|
body = Internal.drop_nil(
|
|
143
159
|
{
|
|
144
160
|
"content" => content,
|
|
@@ -160,6 +176,7 @@ module OmniSocials
|
|
|
160
176
|
"x" => x,
|
|
161
177
|
"bluesky" => bluesky,
|
|
162
178
|
"mastodon" => mastodon,
|
|
179
|
+
"threads" => threads,
|
|
163
180
|
"google_business" => google_business,
|
|
164
181
|
"linkedin_poll" => linkedin_poll
|
|
165
182
|
}
|
|
@@ -190,6 +207,29 @@ module OmniSocials
|
|
|
190
207
|
@client.request("POST", "/posts/#{post_id}/retry")
|
|
191
208
|
end
|
|
192
209
|
|
|
210
|
+
# POST /posts/{id}/approve - approve the current step of a post's
|
|
211
|
+
# approval workflow, on behalf of the user who owns this API key. That
|
|
212
|
+
# user must be a listed approver for the workflow's CURRENT step -
|
|
213
|
+
# steps approve in order, so an approver on a later step gets a 403
|
|
214
|
+
# "forbidden" error until earlier steps clear. Only works on a post
|
|
215
|
+
# with approval_status "pending". If this is the last step, the post
|
|
216
|
+
# finalizes immediately ("scheduled" or "posting"); otherwise it stays
|
|
217
|
+
# "in_approval" and the next step's approvers are notified.
|
|
218
|
+
def approve(post_id)
|
|
219
|
+
@client.request("POST", "/posts/#{post_id}/approve")
|
|
220
|
+
end
|
|
221
|
+
|
|
222
|
+
# POST /posts/{id}/reject - reject a post's approval workflow, on
|
|
223
|
+
# behalf of the user who owns this API key. Same approver requirement
|
|
224
|
+
# as #approve. Unlike approval, a rejection stops the WHOLE workflow
|
|
225
|
+
# immediately (not just the current step) - the post's status becomes
|
|
226
|
+
# "rejected". `comment` is optional and, when given, is shown to the
|
|
227
|
+
# requester and other approvers in the post's review thread.
|
|
228
|
+
def reject(post_id, comment: nil)
|
|
229
|
+
body = comment ? { comment: comment } : nil
|
|
230
|
+
@client.request("POST", "/posts/#{post_id}/reject", json: body)
|
|
231
|
+
end
|
|
232
|
+
|
|
193
233
|
private
|
|
194
234
|
|
|
195
235
|
def create_body(content:, channels:, scheduled_at:, media_ids:,
|
|
@@ -199,7 +239,7 @@ module OmniSocials
|
|
|
199
239
|
hashtag_set_id:, hashtag_placement:, hashtag_platforms:,
|
|
200
240
|
pinterest:, youtube:, instagram:, facebook:, linkedin:,
|
|
201
241
|
linkedin_page:, tiktok:, x:, bluesky:, mastodon:,
|
|
202
|
-
google_business:, linkedin_poll:)
|
|
242
|
+
threads:, google_business:, linkedin_poll:)
|
|
203
243
|
Internal.drop_nil(
|
|
204
244
|
{
|
|
205
245
|
"content" => content,
|
|
@@ -230,6 +270,7 @@ module OmniSocials
|
|
|
230
270
|
"x" => x,
|
|
231
271
|
"bluesky" => bluesky,
|
|
232
272
|
"mastodon" => mastodon,
|
|
273
|
+
"threads" => threads,
|
|
233
274
|
"google_business" => google_business,
|
|
234
275
|
"linkedin_poll" => linkedin_poll
|
|
235
276
|
}
|
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.5.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-08-
|
|
11
|
+
date: 2026-08-30 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,
|