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.
@@ -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
@@ -0,0 +1,5 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Posthubify
4
+ VERSION = '0.1.0'
5
+ 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: []