posthubify 0.1.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 +7 -0
- data/README.md +76 -0
- data/lib/posthubify/client.rb +138 -0
- data/lib/posthubify/errors.rb +21 -0
- data/lib/posthubify/http.rb +102 -0
- data/lib/posthubify/resources/accounts.rb +185 -0
- data/lib/posthubify/resources/ads.rb +226 -0
- data/lib/posthubify/resources/analytics.rb +170 -0
- data/lib/posthubify/resources/messaging.rb +882 -0
- data/lib/posthubify/resources/platform.rb +227 -0
- data/lib/posthubify/resources/posts.rb +102 -0
- data/lib/posthubify/resources/telecom.rb +86 -0
- data/lib/posthubify/version.rb +5 -0
- data/lib/posthubify.rb +16 -0
- metadata +57 -0
|
@@ -0,0 +1,227 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Posthubify
|
|
4
|
+
# Webhook subscriptions (Node sdk .webhooks).
|
|
5
|
+
class WebhooksResource
|
|
6
|
+
def initialize(http)
|
|
7
|
+
@http = http
|
|
8
|
+
end
|
|
9
|
+
|
|
10
|
+
# List all webhooks.
|
|
11
|
+
def list
|
|
12
|
+
@http.data('GET', '/webhooks')
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
# Create a new webhook (empty events → all events; store the secret from the response).
|
|
16
|
+
def create(input)
|
|
17
|
+
@http.data('POST', '/webhooks', body: input)
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
# Update a webhook (url/events/isActive).
|
|
21
|
+
def update(id, input)
|
|
22
|
+
@http.data('PATCH', "/webhooks/#{id}", body: input)
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
# Delete a webhook.
|
|
26
|
+
def delete(id)
|
|
27
|
+
@http.data('DELETE', "/webhooks/#{id}")
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
# Send a signed webhook.test delivery — setup verification.
|
|
31
|
+
def test(id)
|
|
32
|
+
@http.data('POST', "/webhooks/#{id}/test")
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
# Delivery attempts (newest first).
|
|
36
|
+
def logs(limit: nil, status: nil)
|
|
37
|
+
@http.data('GET', '/webhooks/logs', query: { 'limit' => limit, 'status' => status })
|
|
38
|
+
end
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
# API keys (Node sdk .apiKeys).
|
|
42
|
+
class ApiKeysResource
|
|
43
|
+
def initialize(http)
|
|
44
|
+
@http = http
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
# List API keys.
|
|
48
|
+
def list
|
|
49
|
+
@http.data('GET', '/api-keys')
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
# Create a new key — the full key is returned ONLY in this response.
|
|
53
|
+
def create(input)
|
|
54
|
+
@http.data('POST', '/api-keys', body: input)
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
# Delete a key.
|
|
58
|
+
def delete(id)
|
|
59
|
+
@http.data('DELETE', "/api-keys/#{id}")
|
|
60
|
+
end
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
# Publishing queue schedules (Node sdk .queue.schedules).
|
|
64
|
+
class QueueSchedulesResource
|
|
65
|
+
def initialize(http)
|
|
66
|
+
@http = http
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
# List schedule patterns.
|
|
70
|
+
def list
|
|
71
|
+
@http.data('GET', '/queue/schedules')
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
# Create a new schedule pattern.
|
|
75
|
+
def create(input)
|
|
76
|
+
@http.data('POST', '/queue/schedules', body: input)
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
# Update a schedule pattern.
|
|
80
|
+
def update(id, input)
|
|
81
|
+
@http.data('PATCH', "/queue/schedules/#{id}", body: input)
|
|
82
|
+
end
|
|
83
|
+
|
|
84
|
+
# Delete a schedule pattern.
|
|
85
|
+
def delete(id)
|
|
86
|
+
@http.data('DELETE', "/queue/schedules/#{id}")
|
|
87
|
+
end
|
|
88
|
+
|
|
89
|
+
# Re-spread the queue when the pattern changes (only add-to-queue posts are moved).
|
|
90
|
+
def reshuffle(id)
|
|
91
|
+
@http.data('POST', "/queue/schedules/#{id}/reshuffle")
|
|
92
|
+
end
|
|
93
|
+
end
|
|
94
|
+
|
|
95
|
+
# Publishing queue (Node sdk .queue). Sub-resource: schedules.
|
|
96
|
+
class QueueResource
|
|
97
|
+
attr_reader :schedules
|
|
98
|
+
|
|
99
|
+
def initialize(http)
|
|
100
|
+
@http = http
|
|
101
|
+
@schedules = QueueSchedulesResource.new(http)
|
|
102
|
+
end
|
|
103
|
+
|
|
104
|
+
# The next EMPTY slot — posts.create(queue: true) lands here.
|
|
105
|
+
def next_slot(profile_id: nil)
|
|
106
|
+
@http.data('GET', '/queue/next-slot', query: { 'profileId' => profile_id })
|
|
107
|
+
end
|
|
108
|
+
|
|
109
|
+
# Preview of upcoming slots (with taken flag).
|
|
110
|
+
def preview(profile_id: nil, count: nil)
|
|
111
|
+
@http.data('GET', '/queue/preview', query: { 'profileId' => profile_id, 'count' => count })
|
|
112
|
+
end
|
|
113
|
+
end
|
|
114
|
+
|
|
115
|
+
# Account groups (Node sdk .accountGroups).
|
|
116
|
+
class AccountGroupsResource
|
|
117
|
+
def initialize(http)
|
|
118
|
+
@http = http
|
|
119
|
+
end
|
|
120
|
+
|
|
121
|
+
# List account groups.
|
|
122
|
+
def list
|
|
123
|
+
@http.data('GET', '/account-groups')
|
|
124
|
+
end
|
|
125
|
+
|
|
126
|
+
# Create a new account group.
|
|
127
|
+
def create(input)
|
|
128
|
+
@http.data('POST', '/account-groups', body: input)
|
|
129
|
+
end
|
|
130
|
+
|
|
131
|
+
# Update an account group (name/accountIds).
|
|
132
|
+
def update(id, input)
|
|
133
|
+
@http.data('PATCH', "/account-groups/#{id}", body: input)
|
|
134
|
+
end
|
|
135
|
+
|
|
136
|
+
# Delete an account group.
|
|
137
|
+
def delete(id)
|
|
138
|
+
@http.data('DELETE', "/account-groups/#{id}")
|
|
139
|
+
end
|
|
140
|
+
end
|
|
141
|
+
|
|
142
|
+
# X engagements (Node sdk .engagement.x) — repost/bookmark/follow + undo.
|
|
143
|
+
class XEngagementResource
|
|
144
|
+
def initialize(http)
|
|
145
|
+
@http = http
|
|
146
|
+
end
|
|
147
|
+
|
|
148
|
+
# Repost a tweet.
|
|
149
|
+
def repost(account_id, tweet_id)
|
|
150
|
+
@http.data('POST', '/engagement/x/retweets', body: { 'accountId' => account_id, 'tweetId' => tweet_id })
|
|
151
|
+
end
|
|
152
|
+
|
|
153
|
+
# Undo a repost.
|
|
154
|
+
def undo_repost(account_id, tweet_id)
|
|
155
|
+
@http.data('DELETE', '/engagement/x/retweets', query: { 'accountId' => account_id, 'tweetId' => tweet_id })
|
|
156
|
+
end
|
|
157
|
+
|
|
158
|
+
# Bookmark a tweet.
|
|
159
|
+
def bookmark(account_id, tweet_id)
|
|
160
|
+
@http.data('POST', '/engagement/x/bookmarks', body: { 'accountId' => account_id, 'tweetId' => tweet_id })
|
|
161
|
+
end
|
|
162
|
+
|
|
163
|
+
# Remove a bookmark.
|
|
164
|
+
def undo_bookmark(account_id, tweet_id)
|
|
165
|
+
@http.data('DELETE', '/engagement/x/bookmarks', query: { 'accountId' => account_id, 'tweetId' => tweet_id })
|
|
166
|
+
end
|
|
167
|
+
|
|
168
|
+
# Follow a target.
|
|
169
|
+
def follow(account_id, target)
|
|
170
|
+
@http.data('POST', '/engagement/x/follows', body: { 'accountId' => account_id, 'target' => target })
|
|
171
|
+
end
|
|
172
|
+
|
|
173
|
+
# Unfollow.
|
|
174
|
+
def unfollow(account_id, target)
|
|
175
|
+
@http.data('DELETE', '/engagement/x/follows', query: { 'accountId' => account_id, 'target' => target })
|
|
176
|
+
end
|
|
177
|
+
end
|
|
178
|
+
|
|
179
|
+
# Platform engagements (Node sdk .engagement). Sub-resource: x.
|
|
180
|
+
class EngagementResource
|
|
181
|
+
attr_reader :x
|
|
182
|
+
|
|
183
|
+
def initialize(http)
|
|
184
|
+
@http = http
|
|
185
|
+
@x = XEngagementResource.new(http)
|
|
186
|
+
end
|
|
187
|
+
end
|
|
188
|
+
|
|
189
|
+
# Team users (Node sdk .users).
|
|
190
|
+
class UsersResource
|
|
191
|
+
def initialize(http)
|
|
192
|
+
@http = http
|
|
193
|
+
end
|
|
194
|
+
|
|
195
|
+
# List team users.
|
|
196
|
+
def list
|
|
197
|
+
@http.data('GET', '/users')
|
|
198
|
+
end
|
|
199
|
+
|
|
200
|
+
# Get a single user.
|
|
201
|
+
def get(id)
|
|
202
|
+
@http.data('GET', "/users/#{id}")
|
|
203
|
+
end
|
|
204
|
+
end
|
|
205
|
+
|
|
206
|
+
# Invite tokens (Node sdk .inviteTokens).
|
|
207
|
+
class InviteTokensResource
|
|
208
|
+
def initialize(http)
|
|
209
|
+
@http = http
|
|
210
|
+
end
|
|
211
|
+
|
|
212
|
+
# List invite tokens.
|
|
213
|
+
def list
|
|
214
|
+
@http.data('GET', '/invite/tokens')
|
|
215
|
+
end
|
|
216
|
+
|
|
217
|
+
# Create a single-use invite link (7 days; input optional).
|
|
218
|
+
def create(input = {})
|
|
219
|
+
@http.data('POST', '/invite/tokens', body: input)
|
|
220
|
+
end
|
|
221
|
+
|
|
222
|
+
# Delete an invite token.
|
|
223
|
+
def delete(id)
|
|
224
|
+
@http.data('DELETE', "/invite/tokens/#{id}")
|
|
225
|
+
end
|
|
226
|
+
end
|
|
227
|
+
end
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Posthubify
|
|
4
|
+
# Post lifecycle — create/list/update/retry/unpublish (Node sdk .posts).
|
|
5
|
+
class PostsResource
|
|
6
|
+
def initialize(http)
|
|
7
|
+
@http = http
|
|
8
|
+
end
|
|
9
|
+
|
|
10
|
+
# List all posts.
|
|
11
|
+
def list
|
|
12
|
+
@http.data('GET', '/posts')
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
# Get a single post.
|
|
16
|
+
def get(id)
|
|
17
|
+
@http.data('GET', "/posts/#{id}")
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
# Create a post. scheduled_at → schedule; publish:true → publish immediately; neither → draft.
|
|
21
|
+
# input = camelCase-keyed Hash (accountIds, content, captions, mediaAssetId, scheduledAt, publish, queue, options).
|
|
22
|
+
def create(input, idempotency_key: nil)
|
|
23
|
+
@http.data('POST', '/posts', body: input, idempotency_key: idempotency_key)
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
# Validate content against per-platform character limits (input: { 'content' => ..., 'platforms' => [...] }).
|
|
27
|
+
def validate(input)
|
|
28
|
+
@http.data('POST', '/validate', body: input)
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
# Cancels unpublished targets (rejected); 409 if all are published.
|
|
32
|
+
def delete(id)
|
|
33
|
+
@http.data('DELETE', "/posts/#{id}")
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
# Retries failed targets; returns a per-target result.
|
|
37
|
+
def retry(id)
|
|
38
|
+
@http.data('POST', "/posts/#{id}/retry")
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
# Edits the content/timing of unpublished targets (input: content/captions/scheduledAt).
|
|
42
|
+
def update(id, input)
|
|
43
|
+
@http.data('PUT', "/posts/#{id}", body: input)
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
# Removes the live post from the platform (platforms with deletePost capability).
|
|
47
|
+
def unpublish(id)
|
|
48
|
+
@http.data('POST', "/posts/#{id}/unpublish")
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
# Edits the text of a published post on the platform (input: content/captions).
|
|
52
|
+
def edit_published(id, input)
|
|
53
|
+
@http.data('POST', "/posts/#{id}/edit-published", body: input)
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
# Bulk CSV upload (max 200 rows). dry_run=true only validates.
|
|
57
|
+
def bulk_upload(csv, dry_run: nil)
|
|
58
|
+
@http.data('POST', '/posts/bulk-upload', body: { 'csv' => csv, 'dryRun' => dry_run })
|
|
59
|
+
end
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
# Media assets — upload + signed presign URL (Node sdk .media).
|
|
63
|
+
class MediaResource
|
|
64
|
+
def initialize(http)
|
|
65
|
+
@http = http
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
# Upload an image/video (max 64MB) → use the assetId with posts.create({ mediaAssetId }).
|
|
69
|
+
# content = raw byte string (binary string).
|
|
70
|
+
def upload(content, filename = 'upload.bin')
|
|
71
|
+
@http.data('POST', '/media', files: ['file', content, filename])
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
# Signed PUT URL for direct-to-storage upload (5GB; requires R2, otherwise 501).
|
|
75
|
+
# input: { 'fileName' => ..., 'contentType' => ... }.
|
|
76
|
+
def presign(input)
|
|
77
|
+
@http.data('POST', '/media/presign', body: input)
|
|
78
|
+
end
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
# Validation tools — post-length/media/subreddit pre-checks (Node sdk .tools).
|
|
82
|
+
class ToolsResource
|
|
83
|
+
def initialize(http)
|
|
84
|
+
@http = http
|
|
85
|
+
end
|
|
86
|
+
|
|
87
|
+
# Per-platform weighted character report (X: URL=23).
|
|
88
|
+
def validate_post_length(text)
|
|
89
|
+
@http.data('POST', '/tools/validate/post-length', body: { 'text' => text })
|
|
90
|
+
end
|
|
91
|
+
|
|
92
|
+
# Media URL pre-check (accessibility/type/size; private network addresses are rejected).
|
|
93
|
+
def validate_media(url)
|
|
94
|
+
@http.data('POST', '/tools/validate/media', body: { 'url' => url })
|
|
95
|
+
end
|
|
96
|
+
|
|
97
|
+
# Whether the subreddit exists + basic info (subscriber count, NSFW, post type).
|
|
98
|
+
def validate_subreddit(subreddit)
|
|
99
|
+
@http.data('GET', '/tools/validate/subreddit', query: { 'subreddit' => subreddit })
|
|
100
|
+
end
|
|
101
|
+
end
|
|
102
|
+
end
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Posthubify
|
|
4
|
+
# Phone number provisioning (Node sdk .numbers).
|
|
5
|
+
class NumbersResource
|
|
6
|
+
def initialize(http)
|
|
7
|
+
@http = http
|
|
8
|
+
end
|
|
9
|
+
|
|
10
|
+
# List countries where a number can be purchased.
|
|
11
|
+
def countries
|
|
12
|
+
@http.data('GET', '/numbers/countries')
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
# List the numbers on the account.
|
|
16
|
+
def list
|
|
17
|
+
@http.data('GET', '/numbers')
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
# Buy a new number ({ 'country' => ..., 'profileId' => ... }).
|
|
21
|
+
def buy(input)
|
|
22
|
+
@http.data('POST', '/numbers', body: input)
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
# Release the number (cancel it).
|
|
26
|
+
def release(id)
|
|
27
|
+
@http.data('DELETE', "/numbers/#{id}")
|
|
28
|
+
end
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
# Alphanumeric sender IDs (Node sdk .senders).
|
|
32
|
+
class SendersResource
|
|
33
|
+
def initialize(http)
|
|
34
|
+
@http = http
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
# List registered senders.
|
|
38
|
+
def list
|
|
39
|
+
@http.data('GET', '/senders')
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
# Register an alphanumeric brand name ({ 'senderId' => ..., 'countries' => [...], 'profileId' => ... }).
|
|
43
|
+
def register(input)
|
|
44
|
+
@http.data('POST', '/senders', body: input)
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
# Release the sender.
|
|
48
|
+
def release(id)
|
|
49
|
+
@http.data('DELETE', "/senders/#{id}")
|
|
50
|
+
end
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
# Branded SMS sending + delivery tracking (Node sdk .sms).
|
|
54
|
+
class SmsResource
|
|
55
|
+
def initialize(http)
|
|
56
|
+
@http = http
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
# Send a branded SMS ({ 'senderId' => ..., 'to' => ..., 'text' => ... }); returns messageId.
|
|
60
|
+
def send(input, idempotency_key: nil)
|
|
61
|
+
@http.data('POST', '/sms', body: input, idempotency_key: idempotency_key)
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
# Delivery status (DLR): messageId or provider message id.
|
|
65
|
+
def get(id)
|
|
66
|
+
@http.data('GET', "/sms/#{id}")
|
|
67
|
+
end
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
# OTP generation + verification (Node sdk .otp).
|
|
71
|
+
class OtpResource
|
|
72
|
+
def initialize(http)
|
|
73
|
+
@http = http
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
# Generate an OTP code + send it via SMS ({ 'senderId' => ..., 'to' => ..., 'appName' => ..., 'locale' => ... }); the code is not returned in the response.
|
|
77
|
+
def send(input, idempotency_key: nil)
|
|
78
|
+
@http.data('POST', '/otp/send', body: input, idempotency_key: idempotency_key)
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
# Verify the code the user entered ({ 'senderId' => ..., 'to' => ..., 'code' => ... }).
|
|
82
|
+
def verify(input)
|
|
83
|
+
@http.data('POST', '/otp/verify', body: input)
|
|
84
|
+
end
|
|
85
|
+
end
|
|
86
|
+
end
|
data/lib/posthubify.rb
ADDED
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative 'posthubify/version'
|
|
4
|
+
require_relative 'posthubify/errors'
|
|
5
|
+
require_relative 'posthubify/http'
|
|
6
|
+
Dir[File.join(__dir__, 'posthubify', 'resources', '*.rb')].sort.each { |f| require f }
|
|
7
|
+
require_relative 'posthubify/client'
|
|
8
|
+
|
|
9
|
+
# The official PostHubify Ruby SDK.
|
|
10
|
+
#
|
|
11
|
+
# require 'posthubify'
|
|
12
|
+
# ph = Posthubify::Client.new(api_key: 'sk_...', base_url: 'https://api.posthubify.com/v1')
|
|
13
|
+
# ph.ping
|
|
14
|
+
# ph.posts.create({ 'content' => 'Hello', 'accountIds' => ['acc_1'] })
|
|
15
|
+
module Posthubify
|
|
16
|
+
end
|
metadata
ADDED
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
|
2
|
+
name: posthubify
|
|
3
|
+
version: !ruby/object:Gem::Version
|
|
4
|
+
version: 0.1.0
|
|
5
|
+
platform: ruby
|
|
6
|
+
authors:
|
|
7
|
+
- PostHubify
|
|
8
|
+
autorequire:
|
|
9
|
+
bindir: bin
|
|
10
|
+
cert_chain: []
|
|
11
|
+
date: 2026-06-18 00:00:00.000000000 Z
|
|
12
|
+
dependencies: []
|
|
13
|
+
description: PostHubify /v1 API client. Zero runtime dependencies (standard library
|
|
14
|
+
only).
|
|
15
|
+
email:
|
|
16
|
+
executables: []
|
|
17
|
+
extensions: []
|
|
18
|
+
extra_rdoc_files: []
|
|
19
|
+
files:
|
|
20
|
+
- README.md
|
|
21
|
+
- lib/posthubify.rb
|
|
22
|
+
- lib/posthubify/client.rb
|
|
23
|
+
- lib/posthubify/errors.rb
|
|
24
|
+
- lib/posthubify/http.rb
|
|
25
|
+
- lib/posthubify/resources/accounts.rb
|
|
26
|
+
- lib/posthubify/resources/ads.rb
|
|
27
|
+
- lib/posthubify/resources/analytics.rb
|
|
28
|
+
- lib/posthubify/resources/messaging.rb
|
|
29
|
+
- lib/posthubify/resources/platform.rb
|
|
30
|
+
- lib/posthubify/resources/posts.rb
|
|
31
|
+
- lib/posthubify/resources/telecom.rb
|
|
32
|
+
- lib/posthubify/version.rb
|
|
33
|
+
homepage: https://posthubify.com
|
|
34
|
+
licenses:
|
|
35
|
+
- MIT
|
|
36
|
+
metadata: {}
|
|
37
|
+
post_install_message:
|
|
38
|
+
rdoc_options: []
|
|
39
|
+
require_paths:
|
|
40
|
+
- lib
|
|
41
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
|
42
|
+
requirements:
|
|
43
|
+
- - ">="
|
|
44
|
+
- !ruby/object:Gem::Version
|
|
45
|
+
version: '2.6'
|
|
46
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
|
47
|
+
requirements:
|
|
48
|
+
- - ">="
|
|
49
|
+
- !ruby/object:Gem::Version
|
|
50
|
+
version: '0'
|
|
51
|
+
requirements: []
|
|
52
|
+
rubygems_version: 3.0.3.1
|
|
53
|
+
signing_key:
|
|
54
|
+
specification_version: 4
|
|
55
|
+
summary: The official PostHubify Ruby SDK — social media publishing, inbox, contacts,
|
|
56
|
+
ads, and telecom (SMS/OTP).
|
|
57
|
+
test_files: []
|