omnisocials 0.9.0 → 0.11.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 +79 -3
- data/lib/omnisocials/client.rb +4 -3
- data/lib/omnisocials/resources/analytics.rb +3 -1
- data/lib/omnisocials/resources/automations.rb +135 -0
- data/lib/omnisocials/resources/media.rb +12 -1
- data/lib/omnisocials/resources/posts.rb +97 -11
- data/lib/omnisocials/version.rb +1 -1
- data/lib/omnisocials.rb +1 -0
- metadata +5 -4
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 473f213709ed52c13f70cd2b84c26b7d92c2355731dfc0fb676d3b0f3949fbac
|
|
4
|
+
data.tar.gz: 423f50628c3c4dae0b98d3c486337a573cb32972af6cdf74cd41a8646899f162
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 5a047afd2262cd53375d22fef8b7c79ed254fddf8e5057cdb4cdd909308821bc19c81b73cddf04ad736cecca998bf0ee0f974f6205d2e0bd4472547ee4464dfa
|
|
7
|
+
data.tar.gz: 27c340cc9e7954eae71506ed026b10b1421130cb59c283cab8fdbac3016759b5e449c406367579acbc3cf4c4c1b70fb151750350d8e6bbb27fe330eaaf49cba3
|
data/README.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# OmniSocials Ruby SDK
|
|
2
2
|
|
|
3
|
-
The official Ruby
|
|
3
|
+
The official Ruby SDK for OmniSocials: post, schedule, reply and report on 12 social platforms (Instagram, Facebook, LinkedIn, X, TikTok, YouTube, Threads, Pinterest, Bluesky, Mastodon, Google Business and Telegram) from one API. Guide: [omnisocials.com/sdk/ruby](https://omnisocials.com/sdk/ruby)
|
|
4
4
|
|
|
5
5
|
Full API reference: [https://docs.omnisocials.com](https://docs.omnisocials.com)
|
|
6
6
|
|
|
@@ -30,7 +30,7 @@ puts post["data"]["id"]
|
|
|
30
30
|
|
|
31
31
|
## Authentication
|
|
32
32
|
|
|
33
|
-
Create an API key in the OmniSocials app under **Settings -> API Keys**. Keys
|
|
33
|
+
Create an API key in the OmniSocials app under **Settings -> API Keys**. Keys start with `omsk_live_` and are bound to one workspace. There are no test keys and no test mode: every key works on your real connected accounts. To try things safely, leave out `scheduled_at` so the post stays a draft (a draft never publishes on its own), or use a separate workspace with test accounts.
|
|
34
34
|
|
|
35
35
|
Pass the key explicitly or set the `OMNISOCIALS_API_KEY` environment variable:
|
|
36
36
|
|
|
@@ -65,6 +65,54 @@ post = client.posts.create_and_publish(
|
|
|
65
65
|
)
|
|
66
66
|
```
|
|
67
67
|
|
|
68
|
+
### Planned drafts
|
|
69
|
+
|
|
70
|
+
Pass `draft: true` with `scheduled_at` to store the post as a draft that keeps its time. It shows on the dashboard calendar at that slot with a dashed outline and the scheduler never publishes it: a person schedules it from the composer, or you do with `update` and `draft: false`. Use it when an agent proposes a slot that a human should confirm. `draft: true` is not allowed with `publish_now` or `approval_workflow_id` (`400 validation_error`). On `update`, `draft: false` without `scheduled_at` is a `400 validation_error` when the draft has no time or its time is in the past (pass a new `scheduled_at` then), and `draft: true` on a scheduled post unschedules it while keeping the time.
|
|
71
|
+
|
|
72
|
+
```ruby
|
|
73
|
+
proposal = client.posts.create(
|
|
74
|
+
content: "Proposed for Friday morning",
|
|
75
|
+
channels: ["instagram", "linkedin"],
|
|
76
|
+
scheduled_at: "2026-08-01T09:00:00Z",
|
|
77
|
+
draft: true
|
|
78
|
+
)
|
|
79
|
+
puts proposal["data"]["status"], proposal["data"]["schedule_at"] # draft, the time is kept
|
|
80
|
+
|
|
81
|
+
# The human agrees: schedule it at the time it already carries
|
|
82
|
+
client.posts.update(proposal["data"]["id"], draft: false)
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
### Comment-to-DM automations
|
|
86
|
+
|
|
87
|
+
Answer a keyword in an Instagram comment with a DM, once per person, with an optional public reply under the comment. Pass `comment_dm_automation:` on `create` (Instagram must be in `channels`) and the automation is bound to that post; it starts listening once the post is live. Needs the opt-in `automations:write` scope on the key. `client.automations` manages the automations themselves (`list`, `get`, `create`, `update`, `delete`) and reads their activity (`list_events`, `automations:read`). On `update`, `comment_dm_automation:` as a Hash replaces the post's automation and an explicit `nil` removes it. Instagram rules: one private reply per comment, within 7 days; OmniSocials caps sends at 180 DMs an hour per account.
|
|
88
|
+
|
|
89
|
+
```ruby
|
|
90
|
+
post = client.posts.create(
|
|
91
|
+
content: "Comment GUIDE and I will send you the link",
|
|
92
|
+
channels: ["instagram"],
|
|
93
|
+
scheduled_at: "2026-08-01T09:00:00Z",
|
|
94
|
+
media_urls: ["https://example.com/guide-cover.jpg"],
|
|
95
|
+
comment_dm_automation: {
|
|
96
|
+
"trigger" => { "keywords" => ["guide"], "match" => "word" },
|
|
97
|
+
"actions" => {
|
|
98
|
+
"dm" => { "text" => "Here is the guide: https://example.com/guide" },
|
|
99
|
+
"public_reply" => { "enabled" => true, "variations" => ["Sent you a DM!", "Check your inbox"] }
|
|
100
|
+
},
|
|
101
|
+
"limits" => { "max_sends" => 500 }
|
|
102
|
+
}
|
|
103
|
+
)
|
|
104
|
+
automation_id = post["data"]["comment_dm_automation"]["id"]
|
|
105
|
+
|
|
106
|
+
# Later: who was answered, and why someone was skipped
|
|
107
|
+
events = client.automations.list_events(automation_id, outcome: "sent")
|
|
108
|
+
puts events["counts"] # { "sent" => 12, "skipped" => 3 }
|
|
109
|
+
events["data"].each do |event|
|
|
110
|
+
puts [event["person"]["username"], event["outcome"], event["reason_label"]].join(" ")
|
|
111
|
+
end
|
|
112
|
+
```
|
|
113
|
+
|
|
114
|
+
The events response is `{ "data", "counts", "pagination" }`, not the plain list: `counts` is the number of events per outcome (`sent`, `skipped`, `failed`, `pending`). An automation can also listen on a post published outside OmniSocials (`client.automations.create(instagram_media_id: ..., actions: ...)`), on the next post the workspace publishes (`trigger: { "scope" => "next_post" }`) or on every post (`"scope" => "any"`), and on DMs and story replies (`trigger: { "kinds" => ["dm", "story_reply"], ... }`).
|
|
115
|
+
|
|
68
116
|
### Per-media alt text
|
|
69
117
|
|
|
70
118
|
Every `media_urls` / `media_ids` entry accepts either a plain String or a Hash with an `alt` accessibility description (max 1500 chars). Alt text is delivered to Mastodon (media description), Bluesky (embed alt), X (photos and GIFs), Pinterest (pin alt text), Instagram (images), and LinkedIn (images). Strings and Hashes can be mixed, and the same shape works in per-platform Hashes and `thread_parts` media.
|
|
@@ -103,6 +151,32 @@ post = client.posts.create(
|
|
|
103
151
|
|
|
104
152
|
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
153
|
|
|
154
|
+
### Telegram channels and groups
|
|
155
|
+
|
|
156
|
+
`telegram` is a channel or group the workspace connected in OmniSocials; the OmniSocials bot posts to it. The `telegram` options: `silent`, `link_preview`, `pin`, `protect_content`, `spoiler` and `caption_above_media` (booleans), up to 3 `buttons` (`{ text, url }`, not on an album or a poll), a native `poll` (2 to 12 options of up to 100 characters, `open_period` 5 to 2628000 seconds, `multiple`; the post text is the question, max 300 characters, no media) and `first_comment` (a reply under the post). Text up to 4,096 characters; with media, text over 1,024 characters is sent as a reply under the media. Albums hold up to 10 photos and videos, files up to 50 MB, no Stories. Once sent, the post's `telegram.result` carries the message ids and the link. Telegram gives bots no view counts: analytics carry the reactions and the poll votes.
|
|
157
|
+
|
|
158
|
+
```ruby
|
|
159
|
+
client.posts.create(
|
|
160
|
+
content: "The new collection is live. Tap the button to shop.",
|
|
161
|
+
channels: ["telegram"],
|
|
162
|
+
scheduled_at: "2026-11-02T09:00:00Z",
|
|
163
|
+
media_urls: ["https://example.com/collection.jpg"],
|
|
164
|
+
telegram: {
|
|
165
|
+
"silent" => true,
|
|
166
|
+
"pin" => true,
|
|
167
|
+
"buttons" => [{ "text" => "Shop now", "url" => "https://example.com/shop" }],
|
|
168
|
+
"first_comment" => "Questions? Reply to this message."
|
|
169
|
+
}
|
|
170
|
+
)
|
|
171
|
+
|
|
172
|
+
# A native poll: the text is the question.
|
|
173
|
+
client.posts.create(
|
|
174
|
+
content: "Which day suits the live Q&A?",
|
|
175
|
+
channels: ["telegram"],
|
|
176
|
+
telegram: { "poll" => { "options" => %w[Tuesday Thursday Saturday], "open_period" => 86_400 } }
|
|
177
|
+
)
|
|
178
|
+
```
|
|
179
|
+
|
|
106
180
|
### Chained threads (X, Bluesky, Mastodon, Threads)
|
|
107
181
|
|
|
108
182
|
Pass 2 to 25 `thread_parts` to publish a chained thread instead of a single tweet (each part is at most 280 characters, 25,000 for X Premium/Premium+ accounts). 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):
|
|
@@ -350,9 +424,11 @@ accounts = client.analytics.accounts(platform: "instagram")
|
|
|
350
424
|
|
|
351
425
|
### Best times to post
|
|
352
426
|
|
|
427
|
+
The response is not wrapped in `data`. `platform`, `timezone`, `basis`, `grid` and `recommendations` sit at the top level.
|
|
428
|
+
|
|
353
429
|
```ruby
|
|
354
430
|
best = client.analytics.best_times(platform: "instagram", timezone: "Europe/Amsterdam")
|
|
355
|
-
best["
|
|
431
|
+
best["recommendations"].each { |slot| puts "#{slot["day"]} #{slot["time"]} #{slot["score"]}" }
|
|
356
432
|
```
|
|
357
433
|
|
|
358
434
|
## Locations (Instagram and Threads place tagging)
|
data/lib/omnisocials/client.rb
CHANGED
|
@@ -60,10 +60,10 @@ module OmniSocials
|
|
|
60
60
|
|
|
61
61
|
attr_reader :api_key, :base_url, :timeout, :max_retries,
|
|
62
62
|
:posts, :media, :folders, :hashtag_sets, :approval_workflows,
|
|
63
|
-
:accounts, :analytics, :audio, :locations, :pinterest,
|
|
64
|
-
:webhooks
|
|
63
|
+
:accounts, :analytics, :audio, :locations, :pinterest,
|
|
64
|
+
:automations, :inbox, :webhooks
|
|
65
65
|
|
|
66
|
-
# api_key - API key (omsk_live_*
|
|
66
|
+
# api_key - API key (omsk_live_*). Falls back to the
|
|
67
67
|
# OMNISOCIALS_API_KEY environment variable. Raises
|
|
68
68
|
# OmniSocials::AuthenticationError when neither is set.
|
|
69
69
|
# base_url - API base URL (default https://api.omnisocials.com/v1).
|
|
@@ -96,6 +96,7 @@ module OmniSocials
|
|
|
96
96
|
@audio = Resources::Audio.new(self)
|
|
97
97
|
@locations = Resources::Locations.new(self)
|
|
98
98
|
@pinterest = Resources::Pinterest.new(self)
|
|
99
|
+
@automations = Resources::Automations.new(self)
|
|
99
100
|
@inbox = Resources::Inbox.new(self)
|
|
100
101
|
@webhooks = Resources::Webhooks.new(self)
|
|
101
102
|
end
|
|
@@ -40,7 +40,9 @@ module OmniSocials
|
|
|
40
40
|
end
|
|
41
41
|
|
|
42
42
|
# GET /analytics/best-times - best times to post for a platform,
|
|
43
|
-
# derived from the workspace's own engagement history.
|
|
43
|
+
# derived from the workspace's own engagement history. The response has
|
|
44
|
+
# no "data" wrapper: "platform", "timezone", "basis", "grid" and
|
|
45
|
+
# "recommendations" sit at the top level.
|
|
44
46
|
def best_times(platform:, timezone: nil)
|
|
45
47
|
@client.request(
|
|
46
48
|
"GET", "/analytics/best-times",
|
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module OmniSocials
|
|
4
|
+
module Resources
|
|
5
|
+
# Automations resource: comment-to-DM automations (list, get, create,
|
|
6
|
+
# update, delete, events). An automation answers a keyword in an
|
|
7
|
+
# Instagram comment (or a DM, or a story reply) with a DM, once per
|
|
8
|
+
# person, with an optional public reply under the comment. The scopes
|
|
9
|
+
# are opt-in on the API key: automations:read for list / get /
|
|
10
|
+
# list_events, automations:write for create / update / delete (and for
|
|
11
|
+
# comment_dm_automation on posts).
|
|
12
|
+
class Automations
|
|
13
|
+
def initialize(client)
|
|
14
|
+
@client = client
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
# GET /automations - list the workspace's comment-to-DM automations,
|
|
18
|
+
# optionally by status ("active", "paused", "completed") or by the
|
|
19
|
+
# OmniSocials post_id they are bound to. Requires the automations:read
|
|
20
|
+
# scope (opt-in on the API key).
|
|
21
|
+
def list(status: nil, post_id: nil)
|
|
22
|
+
@client.request(
|
|
23
|
+
"GET", "/automations",
|
|
24
|
+
query: { "status" => status, "post_id" => post_id }
|
|
25
|
+
)
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
# GET /automations/{id} - fetch a single automation. Requires the
|
|
29
|
+
# automations:read scope (opt-in on the API key).
|
|
30
|
+
def get(automation_id)
|
|
31
|
+
@client.request("GET", "/automations/#{automation_id}")
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
# POST /automations - create a comment-to-DM automation.
|
|
35
|
+
#
|
|
36
|
+
# A keyword in an Instagram comment (or a DM, or a story reply) is
|
|
37
|
+
# answered with a DM, once per person, with an optional public reply
|
|
38
|
+
# under the comment. Bind it to one post with post_id (an OmniSocials
|
|
39
|
+
# post that goes to Instagram, scheduled or live; one automation per
|
|
40
|
+
# post) or instagram_media_id (a post published outside OmniSocials),
|
|
41
|
+
# not both; a trigger scope of "next_post" or "any" needs neither.
|
|
42
|
+
#
|
|
43
|
+
# `actions` is required: { "dm" => { "text" => "..." } } (max 1000
|
|
44
|
+
# characters) plus an optional "public_reply" => { "enabled" => true,
|
|
45
|
+
# "variations" => [...] } (comment kind only, up to 10 of 300
|
|
46
|
+
# characters). `trigger` is { "kinds" => ["comment"], "scope" =>
|
|
47
|
+
# "post", "keywords" => [...], "match" => "word" }: kinds one or more
|
|
48
|
+
# of "comment", "dm", "story_reply"; keywords up to 20 of 60
|
|
49
|
+
# characters, required unless match is "any"; match is "word",
|
|
50
|
+
# "contains" or "any". `limits` is { "max_sends" => 500, "stop_at" =>
|
|
51
|
+
# "2026-12-31T00:00:00Z" } (both optional). `status` is "active"
|
|
52
|
+
# (default) or "paused". Instagram rules: one private reply per
|
|
53
|
+
# comment, within 7 days; OmniSocials caps sends at 180 DMs an hour per
|
|
54
|
+
# account.
|
|
55
|
+
#
|
|
56
|
+
# Requires the automations:write scope (opt-in on the API key).
|
|
57
|
+
# Errors: 400 validation_error, 400 post_not_instagram, 400
|
|
58
|
+
# post_required, 404 post_not_found, 404 instagram_media_not_found,
|
|
59
|
+
# 409 instagram_not_connected, 409 instagram_needs_reauth, 409
|
|
60
|
+
# automation_exists (the error body also carries automation_id).
|
|
61
|
+
def create(actions:, trigger: nil, limits: nil, name: nil, post_id: nil,
|
|
62
|
+
instagram_media_id: nil, status: nil)
|
|
63
|
+
body = automation_body(
|
|
64
|
+
name: name, post_id: post_id, instagram_media_id: instagram_media_id,
|
|
65
|
+
trigger: trigger, actions: actions, limits: limits, status: status
|
|
66
|
+
)
|
|
67
|
+
@client.request("POST", "/automations", json: body)
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
# PATCH /automations/{id} - partial update.
|
|
71
|
+
#
|
|
72
|
+
# Each section (trigger, actions, limits) is merged into the stored
|
|
73
|
+
# one; keys you omit keep their value: new trigger "keywords" keep the
|
|
74
|
+
# kinds, the scope and the match; a new actions "dm" "text" keeps the
|
|
75
|
+
# public reply; limits: { "stop_at" => ... } keeps max_sends.
|
|
76
|
+
# "public_reply" => { "enabled" => false } or { "variations" => [] }
|
|
77
|
+
# turns the public reply off; limits: { "max_sends" => nil } removes
|
|
78
|
+
# that limit. status: "paused" pauses and "active" resumes; another
|
|
79
|
+
# post_id / instagram_media_id moves the automation to that post. A
|
|
80
|
+
# "completed" automation starts again when its limits or trigger
|
|
81
|
+
# change. Requires the automations:write scope. 400 no_fields when
|
|
82
|
+
# nothing is passed.
|
|
83
|
+
def update(automation_id, name: nil, post_id: nil, instagram_media_id: nil,
|
|
84
|
+
trigger: nil, actions: nil, limits: nil, status: nil)
|
|
85
|
+
body = automation_body(
|
|
86
|
+
name: name, post_id: post_id, instagram_media_id: instagram_media_id,
|
|
87
|
+
trigger: trigger, actions: actions, limits: limits, status: status
|
|
88
|
+
)
|
|
89
|
+
@client.request("PATCH", "/automations/#{automation_id}", json: body)
|
|
90
|
+
end
|
|
91
|
+
|
|
92
|
+
# DELETE /automations/{id} - delete an automation. Returns nil (204).
|
|
93
|
+
# Requires the automations:write scope.
|
|
94
|
+
def delete(automation_id)
|
|
95
|
+
@client.request("DELETE", "/automations/#{automation_id}")
|
|
96
|
+
end
|
|
97
|
+
|
|
98
|
+
# GET /automations/{id}/events - the automation's activity: one event
|
|
99
|
+
# per person it answered, skipped or failed on. outcome filters on
|
|
100
|
+
# "sent", "skipped", "failed" or "pending"; limit is 1..100 (default
|
|
101
|
+
# 25), offset skips. Requires the automations:read scope.
|
|
102
|
+
#
|
|
103
|
+
# The response is not the plain list envelope: { "data" => [...],
|
|
104
|
+
# "counts" => { "sent" => 12, "skipped" => 3 }, "pagination" => {...} },
|
|
105
|
+
# where counts is the number of events per outcome (an outcome with no
|
|
106
|
+
# events is left out). Each event carries kind ("comment", "dm",
|
|
107
|
+
# "story_reply"), outcome, reason / reason_label, person (id,
|
|
108
|
+
# username), trigger_id, trigger_text, message (the DM sent),
|
|
109
|
+
# public_reply, platform_message_id, created_at and sent_at.
|
|
110
|
+
def list_events(automation_id, outcome: nil, limit: nil, offset: nil)
|
|
111
|
+
@client.request(
|
|
112
|
+
"GET", "/automations/#{automation_id}/events",
|
|
113
|
+
query: { "outcome" => outcome, "limit" => limit, "offset" => offset }
|
|
114
|
+
)
|
|
115
|
+
end
|
|
116
|
+
|
|
117
|
+
private
|
|
118
|
+
|
|
119
|
+
def automation_body(name:, post_id:, instagram_media_id:, trigger:,
|
|
120
|
+
actions:, limits:, status:)
|
|
121
|
+
Internal.drop_nil(
|
|
122
|
+
{
|
|
123
|
+
"name" => name,
|
|
124
|
+
"post_id" => post_id,
|
|
125
|
+
"instagram_media_id" => instagram_media_id,
|
|
126
|
+
"trigger" => trigger,
|
|
127
|
+
"actions" => actions,
|
|
128
|
+
"limits" => limits,
|
|
129
|
+
"status" => status
|
|
130
|
+
}
|
|
131
|
+
)
|
|
132
|
+
end
|
|
133
|
+
end
|
|
134
|
+
end
|
|
135
|
+
end
|
|
@@ -8,7 +8,8 @@ module OmniSocials
|
|
|
8
8
|
@client = client
|
|
9
9
|
end
|
|
10
10
|
|
|
11
|
-
# GET /media - list library files.
|
|
11
|
+
# GET /media - list library files. Each item carries "width" and
|
|
12
|
+
# "height" (pixels; nil for videos and older files).
|
|
12
13
|
def list(limit: nil, offset: nil, search: nil, folder_id: nil)
|
|
13
14
|
@client.request(
|
|
14
15
|
"GET", "/media",
|
|
@@ -98,6 +99,16 @@ module OmniSocials
|
|
|
98
99
|
#
|
|
99
100
|
# Provide one of: a public `url`, an existing `media_id`, or
|
|
100
101
|
# `size_bytes` + `mime`.
|
|
102
|
+
#
|
|
103
|
+
# The result carries "file" (size_bytes, mime, size_known, width,
|
|
104
|
+
# height; the pixel size is known for library images, nil otherwise),
|
|
105
|
+
# "connected_platforms", "compatible", "warnings", "summary" and "fit":
|
|
106
|
+
# one entry per connected channel with a fit rule for an IMAGE as a
|
|
107
|
+
# feed post (platform, display_name, fits, ratio, shown = "whole" |
|
|
108
|
+
# "cut", target, default_action = "crop" | "keep" | nil, message).
|
|
109
|
+
# fits false = the ratio is outside the window the channel shows
|
|
110
|
+
# uncropped; default_action is what a post create does unless the
|
|
111
|
+
# post's media_fit says otherwise. Empty for videos and unknown sizes.
|
|
101
112
|
def check(url: nil, media_id: nil, size_bytes: nil, mime: nil)
|
|
102
113
|
body = Internal.drop_nil(
|
|
103
114
|
{
|
|
@@ -46,6 +46,25 @@ module OmniSocials
|
|
|
46
46
|
# POST /posts/create - create a post (draft, or scheduled when
|
|
47
47
|
# scheduled_at is set).
|
|
48
48
|
#
|
|
49
|
+
# draft: true with scheduled_at stores a planned draft: the post is
|
|
50
|
+
# "draft" and keeps that time, shows on the dashboard calendar at that
|
|
51
|
+
# slot with a dashed outline, and the scheduler never publishes it. A
|
|
52
|
+
# person schedules it from the composer, or you do with #update and
|
|
53
|
+
# draft: false. Use it when an agent proposes a slot that a human
|
|
54
|
+
# should confirm. Not allowed with publish_now or approval_workflow_id
|
|
55
|
+
# (400 validation_error). Without scheduled_at it changes nothing.
|
|
56
|
+
#
|
|
57
|
+
# media_fit is the image fit per channel for feed images whose ratio is
|
|
58
|
+
# outside what a channel shows uncropped: a Hash of platform id
|
|
59
|
+
# ("instagram", "pinterest", ...) to "auto", "crop" or "keep". auto
|
|
60
|
+
# (default): Instagram crops a tall or wide image to 4:5 / 1.91:1 around
|
|
61
|
+
# the subject (Instagram cuts it to that window anyway and Meta cannot
|
|
62
|
+
# boost a feed post outside it); Pinterest keeps a Pin taller than 2:3
|
|
63
|
+
# and reports it. crop / keep force it per channel. Every decision comes
|
|
64
|
+
# back in the same "warnings" array with code "image_fit" (action is
|
|
65
|
+
# "cropped", "kept" or "crop_failed", with platform, media_url, ratio,
|
|
66
|
+
# target and crop_url).
|
|
67
|
+
#
|
|
49
68
|
# approval_workflow_id (a workflow id from client.approval_workflows.list)
|
|
50
69
|
# routes the post through a saved approval workflow: it is created as
|
|
51
70
|
# in_approval (approval_status "pending") instead of scheduled, the
|
|
@@ -96,6 +115,33 @@ module OmniSocials
|
|
|
96
115
|
# and the Post's "pinterest" block then carries "product_tags_result"
|
|
97
116
|
# (requested, tagged, skipped, error). More than 24 entries or an
|
|
98
117
|
# invalid entry raises a 400 validation_error.
|
|
118
|
+
#
|
|
119
|
+
# telegram holds the Telegram options (the connected channel or group,
|
|
120
|
+
# posted by the OmniSocials bot): "silent", "link_preview", "pin",
|
|
121
|
+
# "protect_content", "spoiler", "caption_above_media" (booleans), up to
|
|
122
|
+
# 3 "buttons" ({ "text" => ..., "url" => ... }, not on an album or a
|
|
123
|
+
# poll), a native "poll" ({ "options" => 2 to 12 of up to 100
|
|
124
|
+
# characters, "open_period" => 5 to 2628000 seconds, "multiple" => true
|
|
125
|
+
# }; the post text is the question, max 300 characters, no media) and
|
|
126
|
+
# "first_comment" (a reply under the post). Text up to 4,096
|
|
127
|
+
# characters; with media, text over 1,024 characters is sent as a reply
|
|
128
|
+
# under the media. No Stories. On #update every key merges into the
|
|
129
|
+
# stored options. Once sent, the Post's "telegram" block carries
|
|
130
|
+
# "result" (message ids and links).
|
|
131
|
+
#
|
|
132
|
+
# comment_dm_automation binds a comment-to-DM automation to the post
|
|
133
|
+
# (Instagram must be in channels): a keyword in a comment is answered
|
|
134
|
+
# with a DM, once per person, with an optional public reply under the
|
|
135
|
+
# comment. Shape: { "trigger" => { "keywords" => ["guide"], "match" =>
|
|
136
|
+
# "word" }, "actions" => { "dm" => { "text" => "..." }, "public_reply"
|
|
137
|
+
# => { "enabled" => true, "variations" => [...] } }, "limits" =>
|
|
138
|
+
# { "max_sends" => 500 } } (see automations.create for the fields).
|
|
139
|
+
# Needs the automations:write scope on the key (403
|
|
140
|
+
# insufficient_scope, checked before anything else); a bad Hash is a
|
|
141
|
+
# 400 validation_error whose message starts with
|
|
142
|
+
# "comment_dm_automation: ". The response then carries
|
|
143
|
+
# data["comment_dm_automation"] (the automation, with "post" nil); it
|
|
144
|
+
# starts listening once the post is live on Instagram.
|
|
99
145
|
def create(content:, channels: nil, scheduled_at: nil, media_ids: nil,
|
|
100
146
|
media_urls: nil, type: nil, source: nil, link_url: nil,
|
|
101
147
|
link_title: nil, link_description: nil, link_thumbnail_url: nil,
|
|
@@ -105,7 +151,9 @@ module OmniSocials
|
|
|
105
151
|
instagram: nil, facebook: nil, linkedin: nil,
|
|
106
152
|
linkedin_page: nil, tiktok: nil, x: nil, bluesky: nil,
|
|
107
153
|
mastodon: nil, threads: nil, google_business: nil,
|
|
108
|
-
|
|
154
|
+
telegram: nil, linkedin_poll: nil,
|
|
155
|
+
approval_workflow_id: nil, draft: nil, media_fit: nil,
|
|
156
|
+
comment_dm_automation: nil)
|
|
109
157
|
body = create_body(
|
|
110
158
|
content: content, channels: channels, scheduled_at: scheduled_at,
|
|
111
159
|
media_ids: media_ids, media_urls: media_urls, type: type,
|
|
@@ -118,15 +166,19 @@ module OmniSocials
|
|
|
118
166
|
youtube: youtube, instagram: instagram, facebook: facebook,
|
|
119
167
|
linkedin: linkedin, linkedin_page: linkedin_page, tiktok: tiktok,
|
|
120
168
|
x: x, bluesky: bluesky, mastodon: mastodon, threads: threads,
|
|
121
|
-
google_business: google_business,
|
|
122
|
-
|
|
169
|
+
google_business: google_business, telegram: telegram,
|
|
170
|
+
linkedin_poll: linkedin_poll,
|
|
171
|
+
approval_workflow_id: approval_workflow_id, draft: draft,
|
|
172
|
+
media_fit: media_fit, comment_dm_automation: comment_dm_automation
|
|
123
173
|
)
|
|
124
174
|
@client.request("POST", "/posts/create", json: body)
|
|
125
175
|
end
|
|
126
176
|
|
|
127
177
|
# POST /posts/create-and-publish - create and publish immediately.
|
|
128
|
-
# See #create for the "warnings" array and the
|
|
129
|
-
# "
|
|
178
|
+
# See #create for the "warnings" array (X link posts, and the
|
|
179
|
+
# "image_fit" entries steered by media_fit), the 402
|
|
180
|
+
# "x_credits_insufficient" credit gate on X link posts and
|
|
181
|
+
# comment_dm_automation.
|
|
130
182
|
def create_and_publish(content:, channels: nil, media_ids: nil,
|
|
131
183
|
media_urls: nil, type: nil, source: nil,
|
|
132
184
|
link_url: nil, link_title: nil, link_description: nil,
|
|
@@ -138,7 +190,8 @@ module OmniSocials
|
|
|
138
190
|
facebook: nil, linkedin: nil, linkedin_page: nil,
|
|
139
191
|
tiktok: nil, x: nil, bluesky: nil, mastodon: nil,
|
|
140
192
|
threads: nil, google_business: nil,
|
|
141
|
-
linkedin_poll: nil
|
|
193
|
+
telegram: nil, linkedin_poll: nil, media_fit: nil,
|
|
194
|
+
comment_dm_automation: nil)
|
|
142
195
|
body = create_body(
|
|
143
196
|
content: content, channels: channels, scheduled_at: nil,
|
|
144
197
|
media_ids: media_ids, media_urls: media_urls, type: type,
|
|
@@ -151,13 +204,29 @@ module OmniSocials
|
|
|
151
204
|
youtube: youtube, instagram: instagram, facebook: facebook,
|
|
152
205
|
linkedin: linkedin, linkedin_page: linkedin_page, tiktok: tiktok,
|
|
153
206
|
x: x, bluesky: bluesky, mastodon: mastodon, threads: threads,
|
|
154
|
-
google_business: google_business,
|
|
207
|
+
google_business: google_business, telegram: telegram,
|
|
208
|
+
linkedin_poll: linkedin_poll,
|
|
209
|
+
media_fit: media_fit, comment_dm_automation: comment_dm_automation
|
|
155
210
|
)
|
|
156
211
|
@client.request("POST", "/posts/create-and-publish", json: body)
|
|
157
212
|
end
|
|
158
213
|
|
|
159
214
|
# PATCH /posts/{id} - update a draft or scheduled post.
|
|
160
215
|
#
|
|
216
|
+
# draft: false without scheduled_at schedules a planned draft at the
|
|
217
|
+
# scheduled_at it already carries (400 validation_error when it has
|
|
218
|
+
# none or it is in the past). draft: true without scheduled_at
|
|
219
|
+
# unschedules a scheduled post: it keeps its time and stays on the
|
|
220
|
+
# calendar as a dashed draft, nothing publishes; a post waiting for
|
|
221
|
+
# approval leaves its workflow. draft: true with scheduled_at moves the
|
|
222
|
+
# time and keeps (or makes) the post a draft.
|
|
223
|
+
#
|
|
224
|
+
# media_fit (same shape as on #create) alone re-decides the stored
|
|
225
|
+
# images: "keep" gives the original back, "crop" crops an image that
|
|
226
|
+
# was kept. Like #create, the response can carry a top-level "warnings"
|
|
227
|
+
# array with "image_fit" entries when the update changes the images or
|
|
228
|
+
# media_fit.
|
|
229
|
+
#
|
|
161
230
|
# Only top-level nils are dropped from the body, so passing e.g.
|
|
162
231
|
# x: { "thread_parts" => nil } still clears an X thread (reverts the
|
|
163
232
|
# post to single-tweet mode). The same applies to bluesky, mastodon
|
|
@@ -169,13 +238,19 @@ module OmniSocials
|
|
|
169
238
|
#
|
|
170
239
|
# See #create for the 402 "x_credits_insufficient" credit gate that
|
|
171
240
|
# can also refuse an update to a scheduled X link post.
|
|
241
|
+
#
|
|
242
|
+
# comment_dm_automation (same shape as on #create) as a Hash replaces
|
|
243
|
+
# the post's comment-to-DM automation, an explicit nil removes it, and
|
|
244
|
+
# leaving it out keeps it. Needs the automations:write scope.
|
|
172
245
|
def update(post_id, content: nil, scheduled_at: nil, channels: nil,
|
|
173
246
|
media_ids: nil, media_urls: nil, type: nil, location_id: nil,
|
|
174
247
|
collaborators: nil, user_tags: nil, pinterest: nil,
|
|
175
248
|
youtube: nil, instagram: nil, facebook: nil, linkedin: nil,
|
|
176
249
|
linkedin_page: nil, tiktok: nil, x: nil, bluesky: nil,
|
|
177
250
|
mastodon: nil, threads: nil, google_business: nil,
|
|
178
|
-
linkedin_poll: nil
|
|
251
|
+
telegram: nil, linkedin_poll: nil, draft: nil,
|
|
252
|
+
media_fit: nil,
|
|
253
|
+
comment_dm_automation: OmniSocials::NOT_GIVEN)
|
|
179
254
|
body = Internal.drop_nil(
|
|
180
255
|
{
|
|
181
256
|
"content" => content,
|
|
@@ -199,9 +274,15 @@ module OmniSocials
|
|
|
199
274
|
"mastodon" => mastodon,
|
|
200
275
|
"threads" => threads,
|
|
201
276
|
"google_business" => google_business,
|
|
202
|
-
"
|
|
277
|
+
"telegram" => telegram,
|
|
278
|
+
"linkedin_poll" => linkedin_poll,
|
|
279
|
+
"draft" => draft,
|
|
280
|
+
"media_fit" => media_fit
|
|
203
281
|
}
|
|
204
282
|
)
|
|
283
|
+
# An explicit nil removes the post's automation, so it must survive
|
|
284
|
+
# drop_nil; NOT_GIVEN (the default) leaves it untouched.
|
|
285
|
+
body["comment_dm_automation"] = comment_dm_automation unless comment_dm_automation.is_a?(NotGiven)
|
|
205
286
|
@client.request("PATCH", "/posts/#{post_id}", json: body)
|
|
206
287
|
end
|
|
207
288
|
|
|
@@ -277,7 +358,8 @@ module OmniSocials
|
|
|
277
358
|
pinterest:, youtube:, instagram:, facebook:, linkedin:,
|
|
278
359
|
linkedin_page:, tiktok:, x:, bluesky:, mastodon:,
|
|
279
360
|
threads:, google_business:, linkedin_poll:,
|
|
280
|
-
approval_workflow_id: nil
|
|
361
|
+
telegram: nil, approval_workflow_id: nil, draft: nil,
|
|
362
|
+
media_fit: nil, comment_dm_automation: nil)
|
|
281
363
|
Internal.drop_nil(
|
|
282
364
|
{
|
|
283
365
|
"content" => content,
|
|
@@ -310,8 +392,12 @@ module OmniSocials
|
|
|
310
392
|
"mastodon" => mastodon,
|
|
311
393
|
"threads" => threads,
|
|
312
394
|
"google_business" => google_business,
|
|
395
|
+
"telegram" => telegram,
|
|
313
396
|
"linkedin_poll" => linkedin_poll,
|
|
314
|
-
"approval_workflow_id" => approval_workflow_id
|
|
397
|
+
"approval_workflow_id" => approval_workflow_id,
|
|
398
|
+
"draft" => draft,
|
|
399
|
+
"media_fit" => media_fit,
|
|
400
|
+
"comment_dm_automation" => comment_dm_automation
|
|
315
401
|
}
|
|
316
402
|
)
|
|
317
403
|
end
|
data/lib/omnisocials/version.rb
CHANGED
data/lib/omnisocials.rb
CHANGED
|
@@ -26,6 +26,7 @@ require_relative "omnisocials/resources/analytics"
|
|
|
26
26
|
require_relative "omnisocials/resources/audio"
|
|
27
27
|
require_relative "omnisocials/resources/locations"
|
|
28
28
|
require_relative "omnisocials/resources/pinterest"
|
|
29
|
+
require_relative "omnisocials/resources/automations"
|
|
29
30
|
require_relative "omnisocials/resources/inbox"
|
|
30
31
|
require_relative "omnisocials/resources/webhooks"
|
|
31
32
|
require_relative "omnisocials/client"
|
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.11.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-10-
|
|
11
|
+
date: 2026-10-21 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,
|
|
@@ -29,6 +29,7 @@ files:
|
|
|
29
29
|
- lib/omnisocials/resources/analytics.rb
|
|
30
30
|
- lib/omnisocials/resources/approval_workflows.rb
|
|
31
31
|
- lib/omnisocials/resources/audio.rb
|
|
32
|
+
- lib/omnisocials/resources/automations.rb
|
|
32
33
|
- lib/omnisocials/resources/folders.rb
|
|
33
34
|
- lib/omnisocials/resources/hashtag_sets.rb
|
|
34
35
|
- lib/omnisocials/resources/inbox.rb
|
|
@@ -39,11 +40,11 @@ files:
|
|
|
39
40
|
- lib/omnisocials/resources/webhooks.rb
|
|
40
41
|
- lib/omnisocials/version.rb
|
|
41
42
|
- lib/omnisocials/webhooks.rb
|
|
42
|
-
homepage: https://omnisocials.com
|
|
43
|
+
homepage: https://omnisocials.com/sdk/ruby
|
|
43
44
|
licenses:
|
|
44
45
|
- MIT
|
|
45
46
|
metadata:
|
|
46
|
-
homepage_uri: https://omnisocials.com
|
|
47
|
+
homepage_uri: https://omnisocials.com/sdk/ruby
|
|
47
48
|
documentation_uri: https://docs.omnisocials.com
|
|
48
49
|
source_code_uri: https://github.com/OmniSocials/omnisocials-ruby
|
|
49
50
|
bug_tracker_uri: https://github.com/OmniSocials/omnisocials-ruby/issues
|