omnisocials 0.1.0 → 0.2.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 +72 -1
- data/lib/omnisocials/client.rb +5 -2
- data/lib/omnisocials/resources/audio.rb +22 -0
- data/lib/omnisocials/resources/hashtag_sets.rb +48 -0
- data/lib/omnisocials/resources/inbox.rb +87 -0
- data/lib/omnisocials/resources/posts.rb +67 -18
- data/lib/omnisocials/version.rb +1 -1
- data/lib/omnisocials.rb +3 -0
- metadata +5 -2
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: cfdc77673c3c6885b9098af3daf33b7393329ed3b6501fd04d28e813585d97b0
|
|
4
|
+
data.tar.gz: 9753cf8ccae71e85a0414fa137d1b201f678c8a7a018edacb2a74df4353dc822
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 7d8390a9fb03fb151002f79d55706c43b29506d604fe84a9c4707cbe233166ebaab7ad4d2a552e1db14de26a8eed0e8244879daf148f2f83bf3a83d5fe7d8449
|
|
7
|
+
data.tar.gz: 0376b9f5a46d25a6cfc9ca718f46ffd45d2cbe1deecdd9430f1bc2ac2b7a45e1299b5b93d54e8dcf3cf98f17f32b379c56eaed9fffb1699aa203493d27d5a4b3
|
data/README.md
CHANGED
|
@@ -65,6 +65,24 @@ post = client.posts.create_and_publish(
|
|
|
65
65
|
)
|
|
66
66
|
```
|
|
67
67
|
|
|
68
|
+
### Per-media alt text
|
|
69
|
+
|
|
70
|
+
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.
|
|
71
|
+
|
|
72
|
+
```ruby
|
|
73
|
+
post = client.posts.create(
|
|
74
|
+
content: "Sunrise over the harbor",
|
|
75
|
+
channels: ["mastodon", "bluesky"],
|
|
76
|
+
scheduled_at: "2026-08-01T09:00:00Z",
|
|
77
|
+
media_urls: [
|
|
78
|
+
{
|
|
79
|
+
"url" => "https://example.com/harbor.jpg",
|
|
80
|
+
"alt" => "A small sailboat crossing a calm harbor at sunrise, sky in deep orange"
|
|
81
|
+
}
|
|
82
|
+
]
|
|
83
|
+
)
|
|
84
|
+
```
|
|
85
|
+
|
|
68
86
|
### Post to specific platforms with platform options
|
|
69
87
|
|
|
70
88
|
`content` can be a per-platform Hash (with `default` as the fallback), and each platform has its own options Hash:
|
|
@@ -107,6 +125,24 @@ post = client.posts.create(
|
|
|
107
125
|
|
|
108
126
|
On update, passing `x: { "thread_parts" => nil }` clears the thread and reverts the post to single-tweet mode. Only top-level `nil` values are dropped from request bodies, so nested `nil` values like this one are sent as JSON `null`.
|
|
109
127
|
|
|
128
|
+
### X link posts use credits
|
|
129
|
+
|
|
130
|
+
X bills API posts whose text contains a URL at a premium, and OmniSocials passes that fee through as prepaid credits (20 credits per URL-containing tweet; threads billed per part with a link). When a create targets X and the text contains a URL, the response Hash includes a top-level `"warnings"` array (a sibling of `"data"`):
|
|
131
|
+
|
|
132
|
+
```ruby
|
|
133
|
+
res = client.posts.create(
|
|
134
|
+
content: "Read the full story: https://example.com/post",
|
|
135
|
+
channels: ["x"]
|
|
136
|
+
)
|
|
137
|
+
(res["warnings"] || []).each do |warning|
|
|
138
|
+
if warning["code"] == "x_url_post_credits"
|
|
139
|
+
puts "#{warning["credits_required"]} credits (balance: #{warning["credits_balance"]})"
|
|
140
|
+
end
|
|
141
|
+
end
|
|
142
|
+
```
|
|
143
|
+
|
|
144
|
+
From `enforce_from` (2026-08-14) the balance is checked at publish time, but credits are only deducted after the post successfully publishes (a failed publish is never charged). If the balance can't cover it, only the X target fails (other platforms publish normally); top up in the dashboard under Settings -> Organisation -> Billing -> Credits, then call `posts.retry`. Posts without links, analytics, and media on X stay free. There is no API endpoint for credits — they are managed in the dashboard.
|
|
145
|
+
|
|
110
146
|
### Other post operations
|
|
111
147
|
|
|
112
148
|
```ruby
|
|
@@ -114,13 +150,18 @@ posts = client.posts.list(status: "scheduled", limit: 50, offset: 0)
|
|
|
114
150
|
post = client.posts.get("123")
|
|
115
151
|
client.posts.update("123", scheduled_at: "2026-08-02T10:00:00Z")
|
|
116
152
|
client.posts.publish("123") # publish a draft or scheduled post now
|
|
153
|
+
client.posts.retry("123") # retry only the failed platforms of a failed/warning post
|
|
117
154
|
client.posts.delete("123") # returns nil (204)
|
|
118
155
|
|
|
119
156
|
# Recent posts fetched live from the connected platforms (including content
|
|
120
|
-
# published outside OmniSocials). Requires the analytics:read scope.
|
|
157
|
+
# published outside OmniSocials). Requires the analytics:read scope. Video
|
|
158
|
+
# records include duration_seconds (whole seconds; TikTok and YouTube today,
|
|
159
|
+
# null for images and platforms that don't expose it).
|
|
121
160
|
recent = client.posts.recent_platform(limit: 10, platforms: ["instagram", "tiktok"])
|
|
122
161
|
```
|
|
123
162
|
|
|
163
|
+
`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.
|
|
164
|
+
|
|
124
165
|
## Media
|
|
125
166
|
|
|
126
167
|
### Upload from a URL (recommended, up to 1GB)
|
|
@@ -190,6 +231,36 @@ client.folders.update(sub["data"]["id"], parent_id: nil) # move to top level
|
|
|
190
231
|
client.folders.delete(folder["data"]["id"]) # files move to root, subfolders move up
|
|
191
232
|
```
|
|
192
233
|
|
|
234
|
+
## Hashtag Sets
|
|
235
|
+
|
|
236
|
+
Save reusable hashtag groups and apply them to posts at create time. Uses the `posts:read` / `posts:write` scopes.
|
|
237
|
+
|
|
238
|
+
```ruby
|
|
239
|
+
set = client.hashtag_sets.create(
|
|
240
|
+
name: "Launch",
|
|
241
|
+
hashtags: ["saas", "buildinpublic", "startup"] # or one string: "#saas #buildinpublic #startup"
|
|
242
|
+
)
|
|
243
|
+
puts set["data"]["preview"] # "#saas #buildinpublic #startup"
|
|
244
|
+
|
|
245
|
+
client.hashtag_sets.list
|
|
246
|
+
client.hashtag_sets.get(set["data"]["id"])
|
|
247
|
+
client.hashtag_sets.update(set["data"]["id"], hashtags: ["saas", "founder"]) # replaces the full list
|
|
248
|
+
client.hashtag_sets.delete(set["data"]["id"]) # returns nil (204)
|
|
249
|
+
```
|
|
250
|
+
|
|
251
|
+
Apply a set when creating a post with `hashtag_set` (the set name, case-insensitive) or `hashtag_set_id`. The set is applied once at create time and tags already in the caption are skipped. `hashtag_placement` is `"caption_append"` (default) or `"first_comment"`, and `hashtag_platforms` restricts the hashtags to a subset of the post's channels. Instagram's 30-hashtag cap returns error code `hashtag_limit_exceeded`.
|
|
252
|
+
|
|
253
|
+
```ruby
|
|
254
|
+
client.posts.create(
|
|
255
|
+
content: "Launch day!",
|
|
256
|
+
channels: ["instagram", "x"],
|
|
257
|
+
scheduled_at: "2026-08-01T09:00:00Z",
|
|
258
|
+
hashtag_set: "Launch",
|
|
259
|
+
hashtag_placement: "first_comment",
|
|
260
|
+
hashtag_platforms: ["instagram"]
|
|
261
|
+
)
|
|
262
|
+
```
|
|
263
|
+
|
|
193
264
|
## Accounts
|
|
194
265
|
|
|
195
266
|
```ruby
|
data/lib/omnisocials/client.rb
CHANGED
|
@@ -59,8 +59,8 @@ module OmniSocials
|
|
|
59
59
|
].freeze
|
|
60
60
|
|
|
61
61
|
attr_reader :api_key, :base_url, :timeout, :max_retries,
|
|
62
|
-
:posts, :media, :folders, :
|
|
63
|
-
:webhooks
|
|
62
|
+
:posts, :media, :folders, :hashtag_sets, :accounts, :analytics,
|
|
63
|
+
:audio, :locations, :inbox, :webhooks
|
|
64
64
|
|
|
65
65
|
# api_key - API key (omsk_live_* / omsk_test_*). Falls back to the
|
|
66
66
|
# OMNISOCIALS_API_KEY environment variable. Raises
|
|
@@ -88,9 +88,12 @@ module OmniSocials
|
|
|
88
88
|
@posts = Resources::Posts.new(self)
|
|
89
89
|
@media = Resources::Media.new(self)
|
|
90
90
|
@folders = Resources::Folders.new(self)
|
|
91
|
+
@hashtag_sets = Resources::HashtagSets.new(self)
|
|
91
92
|
@accounts = Resources::Accounts.new(self)
|
|
92
93
|
@analytics = Resources::Analytics.new(self)
|
|
94
|
+
@audio = Resources::Audio.new(self)
|
|
93
95
|
@locations = Resources::Locations.new(self)
|
|
96
|
+
@inbox = Resources::Inbox.new(self)
|
|
94
97
|
@webhooks = Resources::Webhooks.new(self)
|
|
95
98
|
end
|
|
96
99
|
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module OmniSocials
|
|
4
|
+
module Resources
|
|
5
|
+
# Audio resource: Instagram Reels audio (Meta's licensed catalog).
|
|
6
|
+
class Audio
|
|
7
|
+
def initialize(client)
|
|
8
|
+
@client = client
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
# GET /audio/search?q=&type= - search Meta's licensed audio catalog for
|
|
12
|
+
# Instagram Reels. Omit query for trending audio; type is "music"
|
|
13
|
+
# (default) or "original_sound". Use a result's audio_id as
|
|
14
|
+
# instagram.audio_id on a reel post (with optional
|
|
15
|
+
# instagram.audio_volume / instagram.video_volume, integers 0-100).
|
|
16
|
+
# preview_url is a temporary URL (~1.5 days); never persist it.
|
|
17
|
+
def search(query: nil, type: nil)
|
|
18
|
+
@client.request("GET", "/audio/search", query: { "q" => query, "type" => type })
|
|
19
|
+
end
|
|
20
|
+
end
|
|
21
|
+
end
|
|
22
|
+
end
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module OmniSocials
|
|
4
|
+
module Resources
|
|
5
|
+
# Hashtag sets resource: saved, reusable hashtag groups applied to posts
|
|
6
|
+
# at create time (via hashtag_set / hashtag_set_id on posts.create).
|
|
7
|
+
class HashtagSets
|
|
8
|
+
def initialize(client)
|
|
9
|
+
@client = client
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
# GET /hashtag-sets - list the workspace's saved hashtag sets.
|
|
13
|
+
def list
|
|
14
|
+
@client.request("GET", "/hashtag-sets")
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
# GET /hashtag-sets/{id} - fetch a single hashtag set.
|
|
18
|
+
def get(hashtag_set_id)
|
|
19
|
+
@client.request("GET", "/hashtag-sets/#{hashtag_set_id}")
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
# POST /hashtag-sets - create a hashtag set.
|
|
23
|
+
#
|
|
24
|
+
# `hashtags` is an Array of tags, or a single String of tags. Apply the
|
|
25
|
+
# set on posts.create via hashtag_set (name, case-insensitive) or
|
|
26
|
+
# hashtag_set_id.
|
|
27
|
+
def create(name:, hashtags:)
|
|
28
|
+
@client.request(
|
|
29
|
+
"POST", "/hashtag-sets",
|
|
30
|
+
json: { "name" => name, "hashtags" => hashtags }
|
|
31
|
+
)
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
# PATCH /hashtag-sets/{id} - rename and/or replace the tags.
|
|
35
|
+
#
|
|
36
|
+
# `hashtags` replaces the FULL list.
|
|
37
|
+
def update(hashtag_set_id, name: nil, hashtags: nil)
|
|
38
|
+
body = Internal.drop_nil({ "name" => name, "hashtags" => hashtags })
|
|
39
|
+
@client.request("PATCH", "/hashtag-sets/#{hashtag_set_id}", json: body)
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
# DELETE /hashtag-sets/{id} - delete a hashtag set. Returns nil (204).
|
|
43
|
+
def delete(hashtag_set_id)
|
|
44
|
+
@client.request("DELETE", "/hashtag-sets/#{hashtag_set_id}")
|
|
45
|
+
end
|
|
46
|
+
end
|
|
47
|
+
end
|
|
48
|
+
end
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "cgi"
|
|
4
|
+
|
|
5
|
+
module OmniSocials
|
|
6
|
+
module Resources
|
|
7
|
+
# Inbox resource: social inbox conversations (DMs, comments, mentions)
|
|
8
|
+
# across connected platforms, plus reading and replying to them.
|
|
9
|
+
#
|
|
10
|
+
# The list endpoints use CURSOR pagination (unlike the offset pagination
|
|
11
|
+
# elsewhere): each response carries pagination["next_cursor"],
|
|
12
|
+
# pagination["has_more"], and pagination["limit"]. Page on by passing the
|
|
13
|
+
# previous response's next_cursor as `cursor` while has_more is true.
|
|
14
|
+
#
|
|
15
|
+
# Conversation ids are URL-encoded for you, so pass them exactly as
|
|
16
|
+
# returned - LinkedIn ids contain ":" and "()" (e.g.
|
|
17
|
+
# "linkedin_comment_urn:li:activity:123").
|
|
18
|
+
class Inbox
|
|
19
|
+
def initialize(client)
|
|
20
|
+
@client = client
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
# GET /inbox/conversations - list conversations, newest activity first.
|
|
24
|
+
#
|
|
25
|
+
# All filters are optional: platform ("instagram", "facebook",
|
|
26
|
+
# "linkedin"), type ("dm", "comment", "mention"), unread (only
|
|
27
|
+
# conversations with unread messages), limit (1-100), and cursor (an
|
|
28
|
+
# opaque cursor from a previous response's pagination["next_cursor"]).
|
|
29
|
+
def list_conversations(platform: nil, type: nil, unread: nil, limit: nil, cursor: nil)
|
|
30
|
+
@client.request(
|
|
31
|
+
"GET", "/inbox/conversations",
|
|
32
|
+
query: {
|
|
33
|
+
"platform" => platform,
|
|
34
|
+
"type" => type,
|
|
35
|
+
"unread" => unread,
|
|
36
|
+
"limit" => limit,
|
|
37
|
+
"cursor" => cursor
|
|
38
|
+
}
|
|
39
|
+
)
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
# GET /inbox/conversations/{id}/messages - full message thread for one
|
|
43
|
+
# conversation, newest first. Cursor-paginated via `limit` / `cursor`.
|
|
44
|
+
def get_messages(conversation_id, limit: nil, cursor: nil)
|
|
45
|
+
@client.request(
|
|
46
|
+
"GET", "/inbox/conversations/#{encode_id(conversation_id)}/messages",
|
|
47
|
+
query: { "limit" => limit, "cursor" => cursor }
|
|
48
|
+
)
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
# POST /inbox/conversations/{id}/read - mark every message in the
|
|
52
|
+
# conversation as read. Returns the count of messages newly marked read.
|
|
53
|
+
def mark_read(conversation_id)
|
|
54
|
+
@client.request("POST", "/inbox/conversations/#{encode_id(conversation_id)}/read")
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
# POST /inbox/conversations/{id}/reply - send a reply into the
|
|
58
|
+
# conversation (a DM message, or a reply to the comment/mention).
|
|
59
|
+
#
|
|
60
|
+
# `text` is required. Optionally attach a single media asset by public
|
|
61
|
+
# URL with `attachment_url` plus `attachment_type` ("image", "video",
|
|
62
|
+
# "audio", or "file"). Returns the created outbound message.
|
|
63
|
+
def reply(conversation_id, text:, attachment_url: nil, attachment_type: nil)
|
|
64
|
+
body = Internal.drop_nil(
|
|
65
|
+
{
|
|
66
|
+
"text" => text,
|
|
67
|
+
"attachment_url" => attachment_url,
|
|
68
|
+
"attachment_type" => attachment_type
|
|
69
|
+
}
|
|
70
|
+
)
|
|
71
|
+
@client.request(
|
|
72
|
+
"POST", "/inbox/conversations/#{encode_id(conversation_id)}/reply",
|
|
73
|
+
json: body
|
|
74
|
+
)
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
private
|
|
78
|
+
|
|
79
|
+
# URL-encode a conversation id for use in a path segment. LinkedIn ids
|
|
80
|
+
# contain ":" and "()", so they must be escaped; spaces become %20
|
|
81
|
+
# (a path segment treats "+" literally, unlike a query string).
|
|
82
|
+
def encode_id(conversation_id)
|
|
83
|
+
CGI.escape(conversation_id.to_s).gsub("+", "%20")
|
|
84
|
+
end
|
|
85
|
+
end
|
|
86
|
+
end
|
|
87
|
+
end
|
|
@@ -6,6 +6,13 @@ module OmniSocials
|
|
|
6
6
|
#
|
|
7
7
|
# `content` is a plain String, or a per-platform Hash with a "default" key.
|
|
8
8
|
# `media_ids` / `media_urls` are a flat Array, or a per-platform Hash.
|
|
9
|
+
# Each entry is a plain String, or a Hash with an "alt" accessibility
|
|
10
|
+
# description (max 1500 chars): `{ "url" => "https://...", "alt" => "..." }`
|
|
11
|
+
# for media_urls, `{ "id" => "...", "alt" => "..." }` for media_ids. Alt
|
|
12
|
+
# text is delivered to Mastodon (media description), Bluesky (embed alt),
|
|
13
|
+
# X (photos/GIFs), Pinterest (pin alt text), Instagram (images), and
|
|
14
|
+
# LinkedIn (images); the same entry shape works inside x/bluesky/mastodon
|
|
15
|
+
# `thread_parts` media.
|
|
9
16
|
class Posts
|
|
10
17
|
def initialize(client)
|
|
11
18
|
@client = client
|
|
@@ -36,36 +43,58 @@ module OmniSocials
|
|
|
36
43
|
|
|
37
44
|
# POST /posts/create - create a post (draft, or scheduled when
|
|
38
45
|
# scheduled_at is set).
|
|
46
|
+
#
|
|
47
|
+
# hashtag_set (set name, case-insensitive) or hashtag_set_id applies a
|
|
48
|
+
# saved hashtag set once at create time; tags already in a caption are
|
|
49
|
+
# skipped; Instagram's 30-hashtag cap returns error code
|
|
50
|
+
# hashtag_limit_exceeded. hashtag_placement is "caption_append"
|
|
51
|
+
# (default) or "first_comment"; hashtag_platforms restricts the tags to
|
|
52
|
+
# a subset of channels.
|
|
53
|
+
#
|
|
54
|
+
# When the post targets X and its text (or any thread part) contains a
|
|
55
|
+
# URL, the response includes a top-level "warnings" array (sibling of
|
|
56
|
+
# "data") with a "x_url_post_credits" entry carrying credits_required
|
|
57
|
+
# and credits_balance: X's link-post fee is passed through as prepaid
|
|
58
|
+
# credits, debited at publish time (from 2026-08-14). Credits are
|
|
59
|
+
# managed in the dashboard, not the API.
|
|
39
60
|
def create(content:, channels: nil, scheduled_at: nil, media_ids: nil,
|
|
40
61
|
media_urls: nil, type: nil, source: nil, link_url: nil,
|
|
41
62
|
link_title: nil, link_description: nil, link_thumbnail_url: nil,
|
|
42
63
|
location_id: nil, collaborators: nil, user_tags: nil,
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
64
|
+
hashtag_set: nil, hashtag_set_id: nil, hashtag_placement: nil,
|
|
65
|
+
hashtag_platforms: nil, pinterest: nil, youtube: nil,
|
|
66
|
+
instagram: nil, facebook: nil, linkedin: nil,
|
|
67
|
+
linkedin_page: nil, tiktok: nil, x: nil, bluesky: nil,
|
|
68
|
+
mastodon: nil, google_business: nil)
|
|
46
69
|
body = create_body(
|
|
47
70
|
content: content, channels: channels, scheduled_at: scheduled_at,
|
|
48
71
|
media_ids: media_ids, media_urls: media_urls, type: type,
|
|
49
72
|
source: source, link_url: link_url, link_title: link_title,
|
|
50
73
|
link_description: link_description, link_thumbnail_url: link_thumbnail_url,
|
|
51
74
|
location_id: location_id, collaborators: collaborators,
|
|
52
|
-
user_tags: user_tags,
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
75
|
+
user_tags: user_tags, hashtag_set: hashtag_set,
|
|
76
|
+
hashtag_set_id: hashtag_set_id, hashtag_placement: hashtag_placement,
|
|
77
|
+
hashtag_platforms: hashtag_platforms, pinterest: pinterest,
|
|
78
|
+
youtube: youtube, instagram: instagram, facebook: facebook,
|
|
79
|
+
linkedin: linkedin, linkedin_page: linkedin_page, tiktok: tiktok,
|
|
80
|
+
x: x, bluesky: bluesky, mastodon: mastodon,
|
|
81
|
+
google_business: google_business
|
|
56
82
|
)
|
|
57
83
|
@client.request("POST", "/posts/create", json: body)
|
|
58
84
|
end
|
|
59
85
|
|
|
60
86
|
# POST /posts/create-and-publish - create and publish immediately.
|
|
87
|
+
# See #create for the "warnings" array on X link posts.
|
|
61
88
|
def create_and_publish(content:, channels: nil, media_ids: nil,
|
|
62
89
|
media_urls: nil, type: nil, source: nil,
|
|
63
90
|
link_url: nil, link_title: nil, link_description: nil,
|
|
64
91
|
link_thumbnail_url: nil, location_id: nil,
|
|
65
|
-
collaborators: nil, user_tags: nil,
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
92
|
+
collaborators: nil, user_tags: nil,
|
|
93
|
+
hashtag_set: nil, hashtag_set_id: nil,
|
|
94
|
+
hashtag_placement: nil, hashtag_platforms: nil,
|
|
95
|
+
pinterest: nil, youtube: nil, instagram: nil,
|
|
96
|
+
facebook: nil, linkedin: nil, linkedin_page: nil,
|
|
97
|
+
tiktok: nil, x: nil, bluesky: nil, mastodon: nil,
|
|
69
98
|
google_business: nil)
|
|
70
99
|
body = create_body(
|
|
71
100
|
content: content, channels: channels, scheduled_at: nil,
|
|
@@ -73,10 +102,13 @@ module OmniSocials
|
|
|
73
102
|
source: source, link_url: link_url, link_title: link_title,
|
|
74
103
|
link_description: link_description, link_thumbnail_url: link_thumbnail_url,
|
|
75
104
|
location_id: location_id, collaborators: collaborators,
|
|
76
|
-
user_tags: user_tags,
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
105
|
+
user_tags: user_tags, hashtag_set: hashtag_set,
|
|
106
|
+
hashtag_set_id: hashtag_set_id, hashtag_placement: hashtag_placement,
|
|
107
|
+
hashtag_platforms: hashtag_platforms, pinterest: pinterest,
|
|
108
|
+
youtube: youtube, instagram: instagram, facebook: facebook,
|
|
109
|
+
linkedin: linkedin, linkedin_page: linkedin_page, tiktok: tiktok,
|
|
110
|
+
x: x, bluesky: bluesky, mastodon: mastodon,
|
|
111
|
+
google_business: google_business
|
|
80
112
|
)
|
|
81
113
|
@client.request("POST", "/posts/create-and-publish", json: body)
|
|
82
114
|
end
|
|
@@ -130,14 +162,27 @@ module OmniSocials
|
|
|
130
162
|
@client.request("POST", "/posts/#{post_id}/publish")
|
|
131
163
|
end
|
|
132
164
|
|
|
165
|
+
# POST /posts/{id}/retry - retry the failed platforms of a "failed" or
|
|
166
|
+
# "warning" (partially failed) post, on the same post.
|
|
167
|
+
#
|
|
168
|
+
# Only the platforms that failed are re-published; platforms that
|
|
169
|
+
# already succeeded are never posted again. Asynchronous: a 200 means
|
|
170
|
+
# the retry is queued - poll `get` for the outcome. Max 3 retries per
|
|
171
|
+
# platform.
|
|
172
|
+
def retry(post_id)
|
|
173
|
+
@client.request("POST", "/posts/#{post_id}/retry")
|
|
174
|
+
end
|
|
175
|
+
|
|
133
176
|
private
|
|
134
177
|
|
|
135
178
|
def create_body(content:, channels:, scheduled_at:, media_ids:,
|
|
136
179
|
media_urls:, type:, source:, link_url:, link_title:,
|
|
137
180
|
link_description:, link_thumbnail_url:, location_id:,
|
|
138
|
-
collaborators:, user_tags:,
|
|
139
|
-
|
|
140
|
-
|
|
181
|
+
collaborators:, user_tags:, hashtag_set:,
|
|
182
|
+
hashtag_set_id:, hashtag_placement:, hashtag_platforms:,
|
|
183
|
+
pinterest:, youtube:, instagram:, facebook:, linkedin:,
|
|
184
|
+
linkedin_page:, tiktok:, x:, bluesky:, mastodon:,
|
|
185
|
+
google_business:)
|
|
141
186
|
Internal.drop_nil(
|
|
142
187
|
{
|
|
143
188
|
"content" => content,
|
|
@@ -154,6 +199,10 @@ module OmniSocials
|
|
|
154
199
|
"location_id" => location_id,
|
|
155
200
|
"collaborators" => collaborators,
|
|
156
201
|
"user_tags" => user_tags,
|
|
202
|
+
"hashtag_set" => hashtag_set,
|
|
203
|
+
"hashtag_set_id" => hashtag_set_id,
|
|
204
|
+
"hashtag_placement" => hashtag_placement,
|
|
205
|
+
"hashtag_platforms" => hashtag_platforms,
|
|
157
206
|
"pinterest" => pinterest,
|
|
158
207
|
"youtube" => youtube,
|
|
159
208
|
"instagram" => instagram,
|
data/lib/omnisocials/version.rb
CHANGED
data/lib/omnisocials.rb
CHANGED
|
@@ -19,9 +19,12 @@ require_relative "omnisocials/webhooks"
|
|
|
19
19
|
require_relative "omnisocials/resources/posts"
|
|
20
20
|
require_relative "omnisocials/resources/media"
|
|
21
21
|
require_relative "omnisocials/resources/folders"
|
|
22
|
+
require_relative "omnisocials/resources/hashtag_sets"
|
|
22
23
|
require_relative "omnisocials/resources/accounts"
|
|
23
24
|
require_relative "omnisocials/resources/analytics"
|
|
25
|
+
require_relative "omnisocials/resources/audio"
|
|
24
26
|
require_relative "omnisocials/resources/locations"
|
|
27
|
+
require_relative "omnisocials/resources/inbox"
|
|
25
28
|
require_relative "omnisocials/resources/webhooks"
|
|
26
29
|
require_relative "omnisocials/client"
|
|
27
30
|
|
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.2.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-08-09 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,
|
|
@@ -27,7 +27,10 @@ files:
|
|
|
27
27
|
- lib/omnisocials/internal.rb
|
|
28
28
|
- lib/omnisocials/resources/accounts.rb
|
|
29
29
|
- lib/omnisocials/resources/analytics.rb
|
|
30
|
+
- lib/omnisocials/resources/audio.rb
|
|
30
31
|
- lib/omnisocials/resources/folders.rb
|
|
32
|
+
- lib/omnisocials/resources/hashtag_sets.rb
|
|
33
|
+
- lib/omnisocials/resources/inbox.rb
|
|
31
34
|
- lib/omnisocials/resources/locations.rb
|
|
32
35
|
- lib/omnisocials/resources/media.rb
|
|
33
36
|
- lib/omnisocials/resources/posts.rb
|