postproxy-sdk 1.1.0 → 1.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 +51 -0
- data/lib/postproxy/client.rb +6 -0
- data/lib/postproxy/constants.rb +3 -1
- data/lib/postproxy/resources/posts.rb +23 -3
- data/lib/postproxy/resources/webhooks.rb +59 -0
- data/lib/postproxy/types.rb +73 -2
- data/lib/postproxy/version.rb +1 -1
- data/lib/postproxy/webhook_signature.rb +28 -0
- data/lib/postproxy.rb +1 -0
- metadata +3 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 665c9b0d7745b0da8ae0bd7864047997816bfa532725ee338588b94f1e6e9724
|
|
4
|
+
data.tar.gz: d9ebae2aa90d310886561d4ec4b2da268a2dcd6d341b59343c1661c3429b656c
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 32384b1c8ca96c498094d59530fd2617a542a911342ef969c93243f31da53afeb06a5d9de458413bfd0ef584c09c53a207743d0a217d49fb1dc097972e2e697e
|
|
7
|
+
data.tar.gz: 15c9e8c42a588f99fe9ef0eb93536c7206f3a90b16d75cd1648bb1497ec5fb3b9e00fae71de3abad3b2a4c2ca442c88aea54ba129b58f6d4dc283d8c9a07de0d
|
data/README.md
CHANGED
|
@@ -92,6 +92,17 @@ post = client.posts.create(
|
|
|
92
92
|
scheduled_at: (Time.now + 3600).iso8601
|
|
93
93
|
)
|
|
94
94
|
|
|
95
|
+
# Create a thread post
|
|
96
|
+
post = client.posts.create(
|
|
97
|
+
"Thread starts here",
|
|
98
|
+
profiles: ["prof-1"],
|
|
99
|
+
thread: [
|
|
100
|
+
{ body: "Second post in the thread" },
|
|
101
|
+
{ body: "Third with media", media: ["https://example.com/img.jpg"] },
|
|
102
|
+
]
|
|
103
|
+
)
|
|
104
|
+
post.thread.each { |child| puts "#{child.id}: #{child.body}" }
|
|
105
|
+
|
|
95
106
|
# Delete a post
|
|
96
107
|
client.posts.delete("post-id")
|
|
97
108
|
```
|
|
@@ -146,6 +157,46 @@ Stats vary by platform:
|
|
|
146
157
|
| TikTok | `impressions`, `likes`, `comments`, `shares` |
|
|
147
158
|
| Pinterest | `impressions`, `likes`, `comments`, `saved`, `outbound_clicks` |
|
|
148
159
|
|
|
160
|
+
## Webhooks
|
|
161
|
+
|
|
162
|
+
```ruby
|
|
163
|
+
# List webhooks
|
|
164
|
+
webhooks = client.webhooks.list.data
|
|
165
|
+
|
|
166
|
+
# Get a webhook
|
|
167
|
+
webhook = client.webhooks.get("wh-id")
|
|
168
|
+
|
|
169
|
+
# Create a webhook
|
|
170
|
+
webhook = client.webhooks.create(
|
|
171
|
+
"https://example.com/webhook",
|
|
172
|
+
events: ["post.published", "post.failed"],
|
|
173
|
+
description: "My webhook"
|
|
174
|
+
)
|
|
175
|
+
puts webhook.id, webhook.secret
|
|
176
|
+
|
|
177
|
+
# Update a webhook
|
|
178
|
+
webhook = client.webhooks.update("wh-id", events: ["post.published"], enabled: false)
|
|
179
|
+
|
|
180
|
+
# Delete a webhook
|
|
181
|
+
client.webhooks.delete("wh-id")
|
|
182
|
+
|
|
183
|
+
# List deliveries
|
|
184
|
+
deliveries = client.webhooks.deliveries("wh-id", page: 1, per_page: 10)
|
|
185
|
+
deliveries.data.each { |d| puts "#{d.event_type}: #{d.success}" }
|
|
186
|
+
```
|
|
187
|
+
|
|
188
|
+
### Signature verification
|
|
189
|
+
|
|
190
|
+
Verify incoming webhook signatures using HMAC-SHA256:
|
|
191
|
+
|
|
192
|
+
```ruby
|
|
193
|
+
PostProxy::WebhookSignature.verify(
|
|
194
|
+
payload: request.body.read,
|
|
195
|
+
signature_header: request.headers["X-PostProxy-Signature"],
|
|
196
|
+
secret: "whsec_..."
|
|
197
|
+
)
|
|
198
|
+
```
|
|
199
|
+
|
|
149
200
|
## Profiles
|
|
150
201
|
|
|
151
202
|
```ruby
|
data/lib/postproxy/client.rb
CHANGED
|
@@ -4,6 +4,7 @@ require "json"
|
|
|
4
4
|
require_relative "resources/posts"
|
|
5
5
|
require_relative "resources/profiles"
|
|
6
6
|
require_relative "resources/profile_groups"
|
|
7
|
+
require_relative "resources/webhooks"
|
|
7
8
|
|
|
8
9
|
module PostProxy
|
|
9
10
|
class Client
|
|
@@ -17,6 +18,7 @@ module PostProxy
|
|
|
17
18
|
@posts = nil
|
|
18
19
|
@profiles = nil
|
|
19
20
|
@profile_groups = nil
|
|
21
|
+
@webhooks = nil
|
|
20
22
|
end
|
|
21
23
|
|
|
22
24
|
def posts
|
|
@@ -31,6 +33,10 @@ module PostProxy
|
|
|
31
33
|
@profile_groups ||= Resources::ProfileGroups.new(self)
|
|
32
34
|
end
|
|
33
35
|
|
|
36
|
+
def webhooks
|
|
37
|
+
@webhooks ||= Resources::Webhooks.new(self)
|
|
38
|
+
end
|
|
39
|
+
|
|
34
40
|
def request(method, path, params: nil, json: nil, data: nil, files: nil, profile_group_id: nil)
|
|
35
41
|
url = "/api#{path}"
|
|
36
42
|
|
data/lib/postproxy/constants.rb
CHANGED
|
@@ -7,7 +7,9 @@ module PostProxy
|
|
|
7
7
|
|
|
8
8
|
PROFILE_STATUSES = %w[active expired inactive].freeze
|
|
9
9
|
|
|
10
|
-
POST_STATUSES = %w[pending draft processing processed scheduled].freeze
|
|
10
|
+
POST_STATUSES = %w[pending draft processing processed scheduled media_processing_failed].freeze
|
|
11
|
+
|
|
12
|
+
MEDIA_STATUSES = %w[pending processed failed].freeze
|
|
11
13
|
|
|
12
14
|
PLATFORM_POST_STATUSES = %w[pending processing published failed deleted].freeze
|
|
13
15
|
|
|
@@ -29,8 +29,11 @@ module PostProxy
|
|
|
29
29
|
end
|
|
30
30
|
|
|
31
31
|
def create(body, profiles:, media: nil, media_files: nil, platforms: nil,
|
|
32
|
-
scheduled_at: nil, draft: nil, profile_group_id: nil)
|
|
33
|
-
|
|
32
|
+
thread: nil, scheduled_at: nil, draft: nil, profile_group_id: nil)
|
|
33
|
+
has_files = media_files && !media_files.empty?
|
|
34
|
+
has_thread_files = thread&.any? { |t| t[:media_files]&.any? }
|
|
35
|
+
|
|
36
|
+
if has_files || has_thread_files
|
|
34
37
|
form_data = { "post[body]" => body }
|
|
35
38
|
form_data["post[scheduled_at]"] = format_time(scheduled_at) if scheduled_at
|
|
36
39
|
form_data["post[draft]"] = draft.to_s if !draft.nil?
|
|
@@ -54,7 +57,7 @@ module PostProxy
|
|
|
54
57
|
end
|
|
55
58
|
end
|
|
56
59
|
|
|
57
|
-
media_files
|
|
60
|
+
media_files&.each do |path|
|
|
58
61
|
path = path.to_s
|
|
59
62
|
filename = File.basename(path)
|
|
60
63
|
content_type = mime_type_for(filename)
|
|
@@ -62,6 +65,22 @@ module PostProxy
|
|
|
62
65
|
files << ["media[]", filename, io, content_type]
|
|
63
66
|
end
|
|
64
67
|
|
|
68
|
+
thread&.each_with_index do |t, i|
|
|
69
|
+
form_data["thread[#{i}][body]"] = t[:body] if t[:body]
|
|
70
|
+
|
|
71
|
+
t[:media]&.each do |m|
|
|
72
|
+
files << ["thread[#{i}][media][]", nil, m, "text/plain"]
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
t[:media_files]&.each do |path|
|
|
76
|
+
path = path.to_s
|
|
77
|
+
filename = File.basename(path)
|
|
78
|
+
content_type = mime_type_for(filename)
|
|
79
|
+
io = File.open(path, "rb")
|
|
80
|
+
files << ["thread[#{i}][media][]", filename, io, content_type]
|
|
81
|
+
end
|
|
82
|
+
end
|
|
83
|
+
|
|
65
84
|
result = @client.request(:post, "/posts",
|
|
66
85
|
data: form_data,
|
|
67
86
|
files: files,
|
|
@@ -75,6 +94,7 @@ module PostProxy
|
|
|
75
94
|
json_body = { post: post_payload, profiles: profiles }
|
|
76
95
|
json_body[:platforms] = platforms.is_a?(PlatformParams) ? platforms.to_h : platforms if platforms
|
|
77
96
|
json_body[:media] = media if media
|
|
97
|
+
json_body[:thread] = thread if thread
|
|
78
98
|
|
|
79
99
|
result = @client.request(:post, "/posts", json: json_body, profile_group_id: profile_group_id)
|
|
80
100
|
end
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
module PostProxy
|
|
2
|
+
module Resources
|
|
3
|
+
class Webhooks
|
|
4
|
+
def initialize(client)
|
|
5
|
+
@client = client
|
|
6
|
+
end
|
|
7
|
+
|
|
8
|
+
def list
|
|
9
|
+
result = @client.request(:get, "/webhooks")
|
|
10
|
+
webhooks = (result[:data] || []).map { |w| Webhook.new(**w) }
|
|
11
|
+
ListResponse.new(data: webhooks)
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
def get(id)
|
|
15
|
+
result = @client.request(:get, "/webhooks/#{id}")
|
|
16
|
+
Webhook.new(**result)
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
def create(url, events:, description: nil)
|
|
20
|
+
json_body = { url: url, events: events }
|
|
21
|
+
json_body[:description] = description if description
|
|
22
|
+
|
|
23
|
+
result = @client.request(:post, "/webhooks", json: json_body)
|
|
24
|
+
Webhook.new(**result)
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
def update(id, url: nil, events: nil, enabled: nil, description: nil)
|
|
28
|
+
json_body = {}
|
|
29
|
+
json_body[:url] = url unless url.nil?
|
|
30
|
+
json_body[:events] = events unless events.nil?
|
|
31
|
+
json_body[:enabled] = enabled unless enabled.nil?
|
|
32
|
+
json_body[:description] = description unless description.nil?
|
|
33
|
+
|
|
34
|
+
result = @client.request(:patch, "/webhooks/#{id}", json: json_body)
|
|
35
|
+
Webhook.new(**result)
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
def delete(id)
|
|
39
|
+
result = @client.request(:delete, "/webhooks/#{id}")
|
|
40
|
+
DeleteResponse.new(**result)
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
def deliveries(id, page: nil, per_page: nil)
|
|
44
|
+
params = {}
|
|
45
|
+
params[:page] = page if page
|
|
46
|
+
params[:per_page] = per_page if per_page
|
|
47
|
+
|
|
48
|
+
result = @client.request(:get, "/webhooks/#{id}/deliveries", params: params)
|
|
49
|
+
deliveries = (result[:data] || []).map { |d| WebhookDelivery.new(**d) }
|
|
50
|
+
PaginatedResponse.new(
|
|
51
|
+
data: deliveries,
|
|
52
|
+
total: result[:total],
|
|
53
|
+
page: result[:page],
|
|
54
|
+
per_page: result[:per_page]
|
|
55
|
+
)
|
|
56
|
+
end
|
|
57
|
+
end
|
|
58
|
+
end
|
|
59
|
+
end
|
data/lib/postproxy/types.rb
CHANGED
|
@@ -84,18 +84,89 @@ module PostProxy
|
|
|
84
84
|
end
|
|
85
85
|
end
|
|
86
86
|
|
|
87
|
+
class Media < Model
|
|
88
|
+
attr_accessor :id, :status, :error_message, :content_type, :source_url, :url
|
|
89
|
+
|
|
90
|
+
def initialize(**attrs)
|
|
91
|
+
@error_message = nil
|
|
92
|
+
@source_url = nil
|
|
93
|
+
@url = nil
|
|
94
|
+
super
|
|
95
|
+
end
|
|
96
|
+
end
|
|
97
|
+
|
|
98
|
+
class ThreadChild < Model
|
|
99
|
+
attr_accessor :id, :body, :media
|
|
100
|
+
|
|
101
|
+
def initialize(**attrs)
|
|
102
|
+
@media = []
|
|
103
|
+
super
|
|
104
|
+
@media = (@media || []).map do |m|
|
|
105
|
+
m.is_a?(Media) ? m : Media.new(**m.transform_keys(&:to_sym))
|
|
106
|
+
end
|
|
107
|
+
end
|
|
108
|
+
end
|
|
109
|
+
|
|
87
110
|
class Post < Model
|
|
88
|
-
attr_accessor :id, :body, :status, :scheduled_at, :created_at, :platforms
|
|
111
|
+
attr_accessor :id, :body, :status, :scheduled_at, :created_at, :media, :platforms, :thread
|
|
89
112
|
|
|
90
113
|
def initialize(**attrs)
|
|
91
114
|
@scheduled_at = nil
|
|
115
|
+
@media = []
|
|
92
116
|
@platforms = []
|
|
117
|
+
@thread = []
|
|
93
118
|
super
|
|
94
119
|
@scheduled_at = parse_time(@scheduled_at)
|
|
95
120
|
@created_at = parse_time(@created_at)
|
|
121
|
+
@media = (@media || []).map do |m|
|
|
122
|
+
m.is_a?(Media) ? m : Media.new(**m.transform_keys(&:to_sym))
|
|
123
|
+
end
|
|
96
124
|
@platforms = (@platforms || []).map do |p|
|
|
97
125
|
p.is_a?(PlatformResult) ? p : PlatformResult.new(**p.transform_keys(&:to_sym))
|
|
98
126
|
end
|
|
127
|
+
@thread = (@thread || []).map do |t|
|
|
128
|
+
t.is_a?(ThreadChild) ? t : ThreadChild.new(**t.transform_keys(&:to_sym))
|
|
129
|
+
end
|
|
130
|
+
end
|
|
131
|
+
|
|
132
|
+
private
|
|
133
|
+
|
|
134
|
+
def parse_time(value)
|
|
135
|
+
return nil if value.nil?
|
|
136
|
+
value.is_a?(Time) ? value : Time.parse(value.to_s)
|
|
137
|
+
end
|
|
138
|
+
end
|
|
139
|
+
|
|
140
|
+
class Webhook < Model
|
|
141
|
+
attr_accessor :id, :url, :events, :enabled, :description, :secret,
|
|
142
|
+
:created_at, :updated_at
|
|
143
|
+
|
|
144
|
+
def initialize(**attrs)
|
|
145
|
+
@events = []
|
|
146
|
+
@description = nil
|
|
147
|
+
@secret = nil
|
|
148
|
+
super
|
|
149
|
+
@created_at = parse_time(@created_at)
|
|
150
|
+
@updated_at = parse_time(@updated_at)
|
|
151
|
+
end
|
|
152
|
+
|
|
153
|
+
private
|
|
154
|
+
|
|
155
|
+
def parse_time(value)
|
|
156
|
+
return nil if value.nil?
|
|
157
|
+
value.is_a?(Time) ? value : Time.parse(value.to_s)
|
|
158
|
+
end
|
|
159
|
+
end
|
|
160
|
+
|
|
161
|
+
class WebhookDelivery < Model
|
|
162
|
+
attr_accessor :id, :event_id, :event_type, :response_status,
|
|
163
|
+
:attempt_number, :success, :attempted_at, :created_at
|
|
164
|
+
|
|
165
|
+
def initialize(**attrs)
|
|
166
|
+
@response_status = nil
|
|
167
|
+
super
|
|
168
|
+
@attempted_at = parse_time(@attempted_at)
|
|
169
|
+
@created_at = parse_time(@created_at)
|
|
99
170
|
end
|
|
100
171
|
|
|
101
172
|
private
|
|
@@ -213,7 +284,7 @@ module PostProxy
|
|
|
213
284
|
end
|
|
214
285
|
|
|
215
286
|
class YouTubeParams < Model
|
|
216
|
-
attr_accessor :format, :title, :privacy_status, :cover_url
|
|
287
|
+
attr_accessor :format, :title, :privacy_status, :cover_url, :made_for_kids
|
|
217
288
|
end
|
|
218
289
|
|
|
219
290
|
class PinterestParams < Model
|
data/lib/postproxy/version.rb
CHANGED
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
require "openssl"
|
|
2
|
+
|
|
3
|
+
module PostProxy
|
|
4
|
+
module WebhookSignature
|
|
5
|
+
def self.verify(payload, signature_header, secret)
|
|
6
|
+
parts = signature_header.split(",").map { |p| p.split("=", 2) }.to_h
|
|
7
|
+
timestamp = parts["t"]
|
|
8
|
+
expected = parts["v1"]
|
|
9
|
+
|
|
10
|
+
return false if timestamp.nil? || expected.nil?
|
|
11
|
+
|
|
12
|
+
signed_payload = "#{timestamp}.#{payload}"
|
|
13
|
+
computed = OpenSSL::HMAC.hexdigest("SHA256", secret, signed_payload)
|
|
14
|
+
|
|
15
|
+
secure_compare(computed, expected)
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
private_class_method def self.secure_compare(a, b)
|
|
19
|
+
return false unless a.bytesize == b.bytesize
|
|
20
|
+
|
|
21
|
+
l = a.unpack("C*")
|
|
22
|
+
r = b.unpack("C*")
|
|
23
|
+
result = 0
|
|
24
|
+
l.zip(r) { |x, y| result |= x ^ y }
|
|
25
|
+
result.zero?
|
|
26
|
+
end
|
|
27
|
+
end
|
|
28
|
+
end
|
data/lib/postproxy.rb
CHANGED
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: postproxy-sdk
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 1.
|
|
4
|
+
version: 1.2.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- PostProxy
|
|
@@ -81,8 +81,10 @@ files:
|
|
|
81
81
|
- lib/postproxy/resources/posts.rb
|
|
82
82
|
- lib/postproxy/resources/profile_groups.rb
|
|
83
83
|
- lib/postproxy/resources/profiles.rb
|
|
84
|
+
- lib/postproxy/resources/webhooks.rb
|
|
84
85
|
- lib/postproxy/types.rb
|
|
85
86
|
- lib/postproxy/version.rb
|
|
87
|
+
- lib/postproxy/webhook_signature.rb
|
|
86
88
|
homepage: https://postproxy.dev
|
|
87
89
|
licenses:
|
|
88
90
|
- MIT
|