postproxy-sdk 1.8.0 → 1.10.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 +124 -2
- data/lib/postproxy/client.rb +18 -0
- data/lib/postproxy/constants.rb +15 -1
- data/lib/postproxy/resources/chats.rb +57 -0
- data/lib/postproxy/resources/comments.rb +8 -0
- data/lib/postproxy/resources/messages.rb +118 -0
- data/lib/postproxy/resources/profile_comments.rb +41 -0
- data/lib/postproxy/types.rb +163 -6
- data/lib/postproxy/version.rb +1 -1
- data/lib/postproxy/webhook_events.rb +40 -1
- metadata +4 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 2852d366a306a698457955ea9e631fb4cd05fbc0f55949a772f0b872b19ce0af
|
|
4
|
+
data.tar.gz: 74845607657bcea29116fe1a3d509daea04f0afba3e0ded079068f03b17d3506
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 0650f24d42af376d663521ced5c78be0fe97dc99d117534a132479a02f8290a18404426d5791ea5f90f3ed786041ccb2d3a15486533b149e7ab0c94324839238
|
|
7
|
+
data.tar.gz: 39699c4a07a238f45fc1e12545dfc97b88d85299f03cd3eb1ba97380a1f7bfa79da004abd170c3da87aa707d486a7a103c298c0b34fb9371d9d0c4ada45e3014
|
data/README.md
CHANGED
|
@@ -264,7 +264,7 @@ PostProxy::WebhookSignature.verify(
|
|
|
264
264
|
|
|
265
265
|
Subscribe to any of these events (or pass `["*"]` for all):
|
|
266
266
|
|
|
267
|
-
`post.processed`, `post.imported`, `platform_post.published`, `platform_post.failed`, `platform_post.failed_waiting_for_retry`, `platform_post.insights`, `profile.connected`, `profile.disconnected`, `profile.stats`, `media.failed`, `comment.created`.
|
|
267
|
+
`post.processed`, `post.imported`, `platform_post.published`, `platform_post.failed`, `platform_post.failed_waiting_for_retry`, `platform_post.insights`, `profile.connected`, `profile.disconnected`, `profile.stats`, `media.failed`, `comment.created`, `profile_comment.created`, `message.received`, `message.sent`, `message.delivered`, `message.read`, `message.edited`, `message.deleted`, `message.failed_waiting_for_retry`, `message.failed`, `reaction.received`.
|
|
268
268
|
|
|
269
269
|
`PostProxy::WebhookEvents.parse` validates the envelope and returns a typed `Event` — `event.data` is the right model for the event:
|
|
270
270
|
|
|
@@ -277,9 +277,20 @@ when "platform_post.published"
|
|
|
277
277
|
puts "Published: #{event.data.platform_id}"
|
|
278
278
|
when "comment.created"
|
|
279
279
|
puts "#{event.data.author_username}: #{event.data.body}"
|
|
280
|
+
when "message.received"
|
|
281
|
+
# MessageEventData — event.data.message is a full Message
|
|
282
|
+
puts "DM from #{event.data.message.chat_id}: #{event.data.message.body}"
|
|
283
|
+
when "reaction.received"
|
|
284
|
+
# ReactionEventData
|
|
285
|
+
puts "#{event.data.action}: #{event.data.reaction} on #{event.data.message.id}"
|
|
286
|
+
when "profile_comment.created"
|
|
287
|
+
# ProfileCommentCreatedData
|
|
288
|
+
puts "#{event.data.author_username}: #{event.data.body}"
|
|
280
289
|
end
|
|
281
290
|
```
|
|
282
291
|
|
|
292
|
+
The direct-message events (`message.*`) carry a `MessageEventData` (`data.message` is a full `Message`); `reaction.received` carries a `ReactionEventData`; `profile_comment.created` carries a `ProfileCommentCreatedData`.
|
|
293
|
+
|
|
283
294
|
## Comments
|
|
284
295
|
|
|
285
296
|
```ruby
|
|
@@ -315,6 +326,97 @@ client.comments.unhide("post-id", "comment-id", profile_id: "profile-id")
|
|
|
315
326
|
# Like / unlike a comment
|
|
316
327
|
client.comments.like("post-id", "comment-id", profile_id: "profile-id")
|
|
317
328
|
client.comments.unlike("post-id", "comment-id", profile_id: "profile-id")
|
|
329
|
+
|
|
330
|
+
# Synced comments may carry media attachments and author metadata
|
|
331
|
+
comment = client.comments.get("post-id", "comment-id", profile_id: "profile-id")
|
|
332
|
+
comment.attachments.each { |att| puts "#{att.type} #{att.url} (#{att.status})" }
|
|
333
|
+
puts comment.metadata[:follower_count] if comment.metadata
|
|
334
|
+
|
|
335
|
+
# Private reply to a comment's author (Instagram/Facebook) — returns a Message
|
|
336
|
+
message = client.comments.private_reply("post-id", "comment-id", profile_id: "profile-id", text: "DM-ing you the details.")
|
|
337
|
+
puts message.chat_id, message.status
|
|
338
|
+
```
|
|
339
|
+
|
|
340
|
+
## Direct Messages
|
|
341
|
+
|
|
342
|
+
Read and send 1:1 messages on DM-capable profiles (Facebook Messenger, Instagram, Telegram, Bluesky). A conversation is a **Chat**; it holds **Messages**. Outbound sends are processed asynchronously (`status` starts as `pending`).
|
|
343
|
+
|
|
344
|
+
```ruby
|
|
345
|
+
# List chats for a profile (paginated, most recent first)
|
|
346
|
+
chats = client.chats.list("profile-id", per_page: 20)
|
|
347
|
+
chats.data.each { |chat| puts "#{chat.participant_username}: #{chat.last_message_at}" }
|
|
348
|
+
|
|
349
|
+
# Find or create a chat with a participant (idempotent)
|
|
350
|
+
chat = client.chats.create("profile-id", "igsid_8675309", participant_username: "jane_doe")
|
|
351
|
+
|
|
352
|
+
# Get a single chat
|
|
353
|
+
chat = client.chats.get(chat.id)
|
|
354
|
+
|
|
355
|
+
# List messages in a chat (filter by direction/status)
|
|
356
|
+
messages = client.messages.list(chat.id, direction: "inbound")
|
|
357
|
+
messages.data.each do |msg|
|
|
358
|
+
puts "#{msg.direction}: #{msg.body}"
|
|
359
|
+
msg.attachments.each { |att| puts " #{att.url}" }
|
|
360
|
+
end
|
|
361
|
+
|
|
362
|
+
# Send a text message (within the 24h window)
|
|
363
|
+
sent = client.messages.send(chat.id, body: "Yes, we ship worldwide!")
|
|
364
|
+
|
|
365
|
+
# Send outside the 24h window with a tag (Facebook/Instagram)
|
|
366
|
+
client.messages.send(chat.id, body: "Following up.", tag: "HUMAN_AGENT")
|
|
367
|
+
|
|
368
|
+
# Send media — by hosted URL or local file
|
|
369
|
+
client.messages.send(chat.id, media: ["https://cdn.example.com/photo.png"])
|
|
370
|
+
client.messages.send(chat.id, media_files: ["./photo.png"])
|
|
371
|
+
|
|
372
|
+
# Telegram: reply threading + inline keyboard
|
|
373
|
+
client.messages.send(
|
|
374
|
+
chat.id,
|
|
375
|
+
body: "Pick one:",
|
|
376
|
+
reply_markup: { inline_keyboard: [[{ text: "Track order", callback_data: "track:1" }]] }
|
|
377
|
+
)
|
|
378
|
+
|
|
379
|
+
# Get / edit (Telegram only) a message
|
|
380
|
+
msg = client.messages.get(sent.id)
|
|
381
|
+
client.messages.edit(sent.id, body: "Updated answer.")
|
|
382
|
+
|
|
383
|
+
# React / unreact (Facebook & Instagram)
|
|
384
|
+
client.messages.react(sent.id, reaction: "love", emoji: "❤️")
|
|
385
|
+
client.messages.unreact(sent.id)
|
|
386
|
+
|
|
387
|
+
# Archive / unarchive a chat (Bluesky only)
|
|
388
|
+
client.chats.archive(chat.id)
|
|
389
|
+
client.chats.unarchive(chat.id)
|
|
390
|
+
|
|
391
|
+
# Private reply to a comment's author (Instagram/Facebook) — returns a Message
|
|
392
|
+
message = client.comments.private_reply("post-id", "comment-id", profile_id: "profile-id", text: "DM-ing you the details.")
|
|
393
|
+
puts message.chat_id, message.status
|
|
394
|
+
```
|
|
395
|
+
|
|
396
|
+
## Profile comments (Google Business reviews)
|
|
397
|
+
|
|
398
|
+
Profile-level comments expose Google Business reviews and replies. Reviews are user-generated — the SDK lets you list/get them and reply to or delete your own replies. Reviews sync twice daily.
|
|
399
|
+
|
|
400
|
+
```ruby
|
|
401
|
+
# List reviews for a profile (paginated)
|
|
402
|
+
reviews = client.profile_comments.list("profile-id")
|
|
403
|
+
reviews.data.each do |review|
|
|
404
|
+
rating = (review.platform_data || {})[:star_rating]
|
|
405
|
+
puts "#{review.author_username} #{rating}: #{review.body}"
|
|
406
|
+
review.replies.each { |r| puts " reply: #{r.body}" }
|
|
407
|
+
end
|
|
408
|
+
|
|
409
|
+
# Filter by placement (location)
|
|
410
|
+
reviews = client.profile_comments.list("profile-id", placement_id: "accounts/123/locations/456")
|
|
411
|
+
|
|
412
|
+
# Get a single review
|
|
413
|
+
review = client.profile_comments.get("profile-id", "review-id")
|
|
414
|
+
|
|
415
|
+
# Reply to a review (parent_id is the review id)
|
|
416
|
+
reply = client.profile_comments.create("profile-id", parent_id: "review-id", text: "Thanks for visiting!")
|
|
417
|
+
|
|
418
|
+
# Delete your reply
|
|
419
|
+
client.profile_comments.delete("profile-id", "reply-id")
|
|
318
420
|
```
|
|
319
421
|
|
|
320
422
|
## Profiles
|
|
@@ -435,7 +537,27 @@ post = client.posts.create(
|
|
|
435
537
|
)
|
|
436
538
|
```
|
|
437
539
|
|
|
438
|
-
Supported platforms: `facebook`, `instagram`, `tiktok`, `linkedin`, `youtube`, `twitter`, `threads`, `pinterest`, `bluesky`, `telegram`. Telegram requires a `chat_id` per post — list channels with `client.profiles.placements(profile_id)`.
|
|
540
|
+
Supported platforms: `facebook`, `instagram`, `tiktok`, `linkedin`, `youtube`, `twitter`, `threads`, `pinterest`, `bluesky`, `telegram`, `google_business`. Telegram requires a `chat_id` per post — list channels with `client.profiles.placements(profile_id)`.
|
|
541
|
+
|
|
542
|
+
### Google Business
|
|
543
|
+
|
|
544
|
+
Google Business posts use a `google_business` entry in `PlatformParams` (a plain hash; no typed struct). The `location_id` is the location resource path returned by `client.profiles.placements()`. Supported formats: `standard`, `event`, `offer`. CTA actions: `LEARN_MORE`, `BOOK`, `ORDER`, `SHOP`, `SIGN_UP`, `CALL`. Media is limited to one image (≤5 MB).
|
|
545
|
+
|
|
546
|
+
```ruby
|
|
547
|
+
client.posts.create(
|
|
548
|
+
"Now open weekends!",
|
|
549
|
+
["gbp-profile-id"],
|
|
550
|
+
media: ["https://example.com/store.jpg"],
|
|
551
|
+
platforms: {
|
|
552
|
+
google_business: {
|
|
553
|
+
format: "standard",
|
|
554
|
+
location_id: "accounts/123/locations/456",
|
|
555
|
+
cta_action_type: "LEARN_MORE",
|
|
556
|
+
cta_url: "https://example.com"
|
|
557
|
+
}
|
|
558
|
+
}
|
|
559
|
+
)
|
|
560
|
+
```
|
|
439
561
|
|
|
440
562
|
## Error Handling
|
|
441
563
|
|
data/lib/postproxy/client.rb
CHANGED
|
@@ -7,6 +7,9 @@ require_relative "resources/profile_groups"
|
|
|
7
7
|
require_relative "resources/webhooks"
|
|
8
8
|
require_relative "resources/queues"
|
|
9
9
|
require_relative "resources/comments"
|
|
10
|
+
require_relative "resources/profile_comments"
|
|
11
|
+
require_relative "resources/chats"
|
|
12
|
+
require_relative "resources/messages"
|
|
10
13
|
|
|
11
14
|
module PostProxy
|
|
12
15
|
class Client
|
|
@@ -23,6 +26,9 @@ module PostProxy
|
|
|
23
26
|
@webhooks = nil
|
|
24
27
|
@queues = nil
|
|
25
28
|
@comments = nil
|
|
29
|
+
@profile_comments = nil
|
|
30
|
+
@chats = nil
|
|
31
|
+
@messages = nil
|
|
26
32
|
end
|
|
27
33
|
|
|
28
34
|
def posts
|
|
@@ -49,6 +55,18 @@ module PostProxy
|
|
|
49
55
|
@comments ||= Resources::Comments.new(self)
|
|
50
56
|
end
|
|
51
57
|
|
|
58
|
+
def profile_comments
|
|
59
|
+
@profile_comments ||= Resources::ProfileComments.new(self)
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
def chats
|
|
63
|
+
@chats ||= Resources::Chats.new(self)
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
def messages
|
|
67
|
+
@messages ||= Resources::Messages.new(self)
|
|
68
|
+
end
|
|
69
|
+
|
|
52
70
|
def request(method, path, params: nil, json: nil, data: nil, files: nil, profile_group_id: nil)
|
|
53
71
|
url = "/api#{path}"
|
|
54
72
|
|
data/lib/postproxy/constants.rb
CHANGED
|
@@ -2,7 +2,7 @@ module PostProxy
|
|
|
2
2
|
DEFAULT_BASE_URL = "https://api.postproxy.dev"
|
|
3
3
|
|
|
4
4
|
PLATFORMS = %w[
|
|
5
|
-
facebook instagram tiktok linkedin youtube twitter threads pinterest bluesky telegram
|
|
5
|
+
facebook instagram tiktok linkedin youtube twitter threads pinterest bluesky telegram google_business
|
|
6
6
|
].freeze
|
|
7
7
|
|
|
8
8
|
PROFILE_STATUSES = %w[active expired inactive].freeze
|
|
@@ -32,6 +32,10 @@ module PostProxy
|
|
|
32
32
|
|
|
33
33
|
TELEGRAM_PARSE_MODES = %w[HTML MarkdownV2].freeze
|
|
34
34
|
|
|
35
|
+
MESSAGE_DIRECTIONS = %w[inbound outbound].freeze
|
|
36
|
+
|
|
37
|
+
MESSAGE_STATUSES = %w[pending published failed_waiting_for_retry failed received].freeze
|
|
38
|
+
|
|
35
39
|
WEBHOOK_EVENT_TYPES = %w[
|
|
36
40
|
post.processed
|
|
37
41
|
post.imported
|
|
@@ -44,5 +48,15 @@ module PostProxy
|
|
|
44
48
|
profile.stats
|
|
45
49
|
media.failed
|
|
46
50
|
comment.created
|
|
51
|
+
profile_comment.created
|
|
52
|
+
message.received
|
|
53
|
+
message.sent
|
|
54
|
+
message.delivered
|
|
55
|
+
message.read
|
|
56
|
+
message.edited
|
|
57
|
+
message.deleted
|
|
58
|
+
message.failed_waiting_for_retry
|
|
59
|
+
message.failed
|
|
60
|
+
reaction.received
|
|
47
61
|
].freeze
|
|
48
62
|
end
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
module PostProxy
|
|
2
|
+
module Resources
|
|
3
|
+
class Chats
|
|
4
|
+
def initialize(client)
|
|
5
|
+
@client = client
|
|
6
|
+
end
|
|
7
|
+
|
|
8
|
+
def list(profile_id, page: nil, per_page: nil, before: nil, after: nil, profile_group_id: nil)
|
|
9
|
+
params = {}
|
|
10
|
+
params[:page] = page if page
|
|
11
|
+
params[:per_page] = per_page if per_page
|
|
12
|
+
params[:before] = format_time(before) if before
|
|
13
|
+
params[:after] = format_time(after) if after
|
|
14
|
+
|
|
15
|
+
result = @client.request(:get, "/profiles/#{profile_id}/chats", params: params, profile_group_id: profile_group_id)
|
|
16
|
+
chats = (result[:data] || []).map { |c| Chat.new(**c) }
|
|
17
|
+
PaginatedResponse.new(
|
|
18
|
+
data: chats,
|
|
19
|
+
total: result[:total],
|
|
20
|
+
page: result[:page],
|
|
21
|
+
per_page: result[:per_page]
|
|
22
|
+
)
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
def create(profile_id, participant_external_id, participant_username: nil, participant_name: nil, profile_group_id: nil)
|
|
26
|
+
json_body = { participant_external_id: participant_external_id }
|
|
27
|
+
json_body[:participant_username] = participant_username if participant_username
|
|
28
|
+
json_body[:participant_name] = participant_name if participant_name
|
|
29
|
+
|
|
30
|
+
result = @client.request(:post, "/profiles/#{profile_id}/chats", json: json_body, profile_group_id: profile_group_id)
|
|
31
|
+
Chat.new(**result)
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
def get(chat_id, profile_group_id: nil)
|
|
35
|
+
result = @client.request(:get, "/chats/#{chat_id}", profile_group_id: profile_group_id)
|
|
36
|
+
Chat.new(**result)
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
def archive(chat_id, profile_group_id: nil)
|
|
40
|
+
result = @client.request(:post, "/chats/#{chat_id}/archive", profile_group_id: profile_group_id)
|
|
41
|
+
Chat.new(**result)
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
def unarchive(chat_id, profile_group_id: nil)
|
|
45
|
+
result = @client.request(:delete, "/chats/#{chat_id}/archive", profile_group_id: profile_group_id)
|
|
46
|
+
Chat.new(**result)
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
private
|
|
50
|
+
|
|
51
|
+
def format_time(value)
|
|
52
|
+
return value if value.is_a?(String)
|
|
53
|
+
value.iso8601
|
|
54
|
+
end
|
|
55
|
+
end
|
|
56
|
+
end
|
|
57
|
+
end
|
|
@@ -57,6 +57,14 @@ module PostProxy
|
|
|
57
57
|
result = @client.request(:post, "/posts/#{post_id}/comments/#{comment_id}/unlike", params: { profile_id: profile_id })
|
|
58
58
|
AcceptedResponse.new(**result)
|
|
59
59
|
end
|
|
60
|
+
|
|
61
|
+
def private_reply(post_id, comment_id, profile_id:, text:)
|
|
62
|
+
result = @client.request(:post, "/posts/#{post_id}/comments/#{comment_id}/private_reply",
|
|
63
|
+
params: { profile_id: profile_id },
|
|
64
|
+
json: { text: text }
|
|
65
|
+
)
|
|
66
|
+
Message.new(**result)
|
|
67
|
+
end
|
|
60
68
|
end
|
|
61
69
|
end
|
|
62
70
|
end
|
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
module PostProxy
|
|
2
|
+
module Resources
|
|
3
|
+
class Messages
|
|
4
|
+
def initialize(client)
|
|
5
|
+
@client = client
|
|
6
|
+
end
|
|
7
|
+
|
|
8
|
+
def list(chat_id, page: nil, per_page: nil, direction: nil, status: nil, profile_group_id: nil)
|
|
9
|
+
params = {}
|
|
10
|
+
params[:page] = page if page
|
|
11
|
+
params[:per_page] = per_page if per_page
|
|
12
|
+
params[:direction] = direction if direction
|
|
13
|
+
params[:status] = status if status
|
|
14
|
+
|
|
15
|
+
result = @client.request(:get, "/chats/#{chat_id}/messages", params: params, profile_group_id: profile_group_id)
|
|
16
|
+
messages = (result[:data] || []).map { |m| Message.new(**m) }
|
|
17
|
+
PaginatedResponse.new(
|
|
18
|
+
data: messages,
|
|
19
|
+
total: result[:total],
|
|
20
|
+
page: result[:page],
|
|
21
|
+
per_page: result[:per_page]
|
|
22
|
+
)
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
def send_message(chat_id, body: nil, media: nil, media_files: nil, tag: nil,
|
|
26
|
+
reply_to_external_id: nil, reply_markup: nil, profile_group_id: nil)
|
|
27
|
+
has_files = media_files && !media_files.empty?
|
|
28
|
+
|
|
29
|
+
if has_files
|
|
30
|
+
form_data = {}
|
|
31
|
+
form_data["body"] = body if body
|
|
32
|
+
form_data["tag"] = tag if tag
|
|
33
|
+
form_data["reply_to_external_id"] = reply_to_external_id if reply_to_external_id
|
|
34
|
+
|
|
35
|
+
files = []
|
|
36
|
+
media&.each do |m|
|
|
37
|
+
files << ["media[]", nil, m, "text/plain"]
|
|
38
|
+
end
|
|
39
|
+
media_files.each do |path|
|
|
40
|
+
path = path.to_s
|
|
41
|
+
filename = File.basename(path)
|
|
42
|
+
content_type = mime_type_for(filename)
|
|
43
|
+
io = File.open(path, "rb")
|
|
44
|
+
files << ["media[]", filename, io, content_type]
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
result = @client.request(:post, "/chats/#{chat_id}/messages",
|
|
48
|
+
data: form_data,
|
|
49
|
+
files: files,
|
|
50
|
+
profile_group_id: profile_group_id
|
|
51
|
+
)
|
|
52
|
+
else
|
|
53
|
+
json_body = {}
|
|
54
|
+
json_body[:body] = body if body
|
|
55
|
+
json_body[:media] = media if media
|
|
56
|
+
json_body[:tag] = tag if tag
|
|
57
|
+
json_body[:reply_to_external_id] = reply_to_external_id if reply_to_external_id
|
|
58
|
+
json_body[:reply_markup] = reply_markup if reply_markup
|
|
59
|
+
|
|
60
|
+
result = @client.request(:post, "/chats/#{chat_id}/messages", json: json_body, profile_group_id: profile_group_id)
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
Message.new(**result)
|
|
64
|
+
end
|
|
65
|
+
alias_method :send, :send_message
|
|
66
|
+
|
|
67
|
+
def get(message_id, profile_group_id: nil)
|
|
68
|
+
result = @client.request(:get, "/messages/#{message_id}", profile_group_id: profile_group_id)
|
|
69
|
+
Message.new(**result)
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
def edit(message_id, body: nil, reply_markup: nil, profile_group_id: nil)
|
|
73
|
+
json_body = {}
|
|
74
|
+
json_body[:body] = body if body
|
|
75
|
+
json_body[:reply_markup] = reply_markup if reply_markup
|
|
76
|
+
|
|
77
|
+
result = @client.request(:patch, "/messages/#{message_id}", json: json_body, profile_group_id: profile_group_id)
|
|
78
|
+
Message.new(**result)
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
def react(message_id, reaction: nil, emoji: nil, profile_group_id: nil)
|
|
82
|
+
json_body = {}
|
|
83
|
+
json_body[:reaction] = reaction if reaction
|
|
84
|
+
json_body[:emoji] = emoji if emoji
|
|
85
|
+
|
|
86
|
+
result = @client.request(:post, "/messages/#{message_id}/react",
|
|
87
|
+
json: json_body.empty? ? nil : json_body,
|
|
88
|
+
profile_group_id: profile_group_id
|
|
89
|
+
)
|
|
90
|
+
Message.new(**result)
|
|
91
|
+
end
|
|
92
|
+
|
|
93
|
+
def unreact(message_id, profile_group_id: nil)
|
|
94
|
+
result = @client.request(:delete, "/messages/#{message_id}/unreact", profile_group_id: profile_group_id)
|
|
95
|
+
Message.new(**result)
|
|
96
|
+
end
|
|
97
|
+
|
|
98
|
+
private
|
|
99
|
+
|
|
100
|
+
def mime_type_for(filename)
|
|
101
|
+
case File.extname(filename).downcase
|
|
102
|
+
when ".jpg", ".jpeg" then "image/jpeg"
|
|
103
|
+
when ".png" then "image/png"
|
|
104
|
+
when ".gif" then "image/gif"
|
|
105
|
+
when ".webp" then "image/webp"
|
|
106
|
+
when ".mp4" then "video/mp4"
|
|
107
|
+
when ".mov" then "video/quicktime"
|
|
108
|
+
when ".avi" then "video/x-msvideo"
|
|
109
|
+
when ".webm" then "video/webm"
|
|
110
|
+
when ".mp3" then "audio/mpeg"
|
|
111
|
+
when ".ogg" then "audio/ogg"
|
|
112
|
+
when ".m4a" then "audio/mp4"
|
|
113
|
+
else "application/octet-stream"
|
|
114
|
+
end
|
|
115
|
+
end
|
|
116
|
+
end
|
|
117
|
+
end
|
|
118
|
+
end
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
module PostProxy
|
|
2
|
+
module Resources
|
|
3
|
+
class ProfileComments
|
|
4
|
+
def initialize(client)
|
|
5
|
+
@client = client
|
|
6
|
+
end
|
|
7
|
+
|
|
8
|
+
def list(profile_id, placement_id: nil, page: nil, per_page: nil)
|
|
9
|
+
params = {}
|
|
10
|
+
params[:placement_id] = placement_id if placement_id
|
|
11
|
+
params[:page] = page if page
|
|
12
|
+
params[:per_page] = per_page if per_page
|
|
13
|
+
|
|
14
|
+
result = @client.request(:get, "/profiles/#{profile_id}/comments", params: params)
|
|
15
|
+
comments = (result[:data] || []).map { |c| ProfileComment.new(**c) }
|
|
16
|
+
PaginatedResponse.new(
|
|
17
|
+
data: comments,
|
|
18
|
+
total: result[:total],
|
|
19
|
+
page: result[:page],
|
|
20
|
+
per_page: result[:per_page]
|
|
21
|
+
)
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
def get(profile_id, comment_id)
|
|
25
|
+
result = @client.request(:get, "/profiles/#{profile_id}/comments/#{comment_id}")
|
|
26
|
+
ProfileComment.new(**result)
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
def create(profile_id, parent_id:, text:)
|
|
30
|
+
result = @client.request(:post, "/profiles/#{profile_id}/comments",
|
|
31
|
+
json: { parent_id: parent_id, text: text })
|
|
32
|
+
ProfileComment.new(**result)
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
def delete(profile_id, comment_id)
|
|
36
|
+
result = @client.request(:delete, "/profiles/#{profile_id}/comments/#{comment_id}")
|
|
37
|
+
AcceptedResponse.new(**result)
|
|
38
|
+
end
|
|
39
|
+
end
|
|
40
|
+
end
|
|
41
|
+
end
|
data/lib/postproxy/types.rb
CHANGED
|
@@ -98,14 +98,31 @@ module PostProxy
|
|
|
98
98
|
end
|
|
99
99
|
end
|
|
100
100
|
|
|
101
|
+
class MediaPlatformError < Model
|
|
102
|
+
attr_accessor :platform, :status, :error, :error_details
|
|
103
|
+
|
|
104
|
+
def initialize(**attrs)
|
|
105
|
+
@error = nil
|
|
106
|
+
@error_details = nil
|
|
107
|
+
super
|
|
108
|
+
@error_details = ErrorDetails.new(**@error_details.transform_keys(&:to_sym)) if @error_details.is_a?(Hash)
|
|
109
|
+
end
|
|
110
|
+
end
|
|
111
|
+
|
|
101
112
|
class Media < Model
|
|
102
|
-
attr_accessor :id, :status, :error_message, :content_type, :source_url, :url
|
|
113
|
+
attr_accessor :id, :status, :error_message, :content_type, :source_url, :url, :platforms
|
|
103
114
|
|
|
104
115
|
def initialize(**attrs)
|
|
105
116
|
@error_message = nil
|
|
106
117
|
@source_url = nil
|
|
107
118
|
@url = nil
|
|
119
|
+
@platforms = nil
|
|
108
120
|
super
|
|
121
|
+
if @platforms.is_a?(Array)
|
|
122
|
+
@platforms = @platforms.map do |p|
|
|
123
|
+
p.is_a?(MediaPlatformError) ? p : MediaPlatformError.new(**p.transform_keys(&:to_sym))
|
|
124
|
+
end
|
|
125
|
+
end
|
|
109
126
|
end
|
|
110
127
|
end
|
|
111
128
|
|
|
@@ -290,25 +307,59 @@ module PostProxy
|
|
|
290
307
|
end
|
|
291
308
|
end
|
|
292
309
|
|
|
310
|
+
class Attachment < Model
|
|
311
|
+
attr_accessor :id, :type, :url, :status, :external_id
|
|
312
|
+
|
|
313
|
+
def initialize(**attrs)
|
|
314
|
+
@url = nil
|
|
315
|
+
@external_id = nil
|
|
316
|
+
super
|
|
317
|
+
end
|
|
318
|
+
end
|
|
319
|
+
|
|
320
|
+
class Reaction < Model
|
|
321
|
+
attr_accessor :sender_external_id, :emoji, :reaction, :at
|
|
322
|
+
|
|
323
|
+
def initialize(**attrs)
|
|
324
|
+
@emoji = nil
|
|
325
|
+
@reaction = nil
|
|
326
|
+
@at = nil
|
|
327
|
+
super
|
|
328
|
+
@at = parse_time(@at)
|
|
329
|
+
end
|
|
330
|
+
|
|
331
|
+
private
|
|
332
|
+
|
|
333
|
+
def parse_time(value)
|
|
334
|
+
return nil if value.nil?
|
|
335
|
+
value.is_a?(Time) ? value : Time.parse(value.to_s)
|
|
336
|
+
end
|
|
337
|
+
end
|
|
338
|
+
|
|
293
339
|
class Comment < Model
|
|
294
340
|
attr_accessor :id, :external_id, :body, :status, :author_username,
|
|
295
|
-
:author_avatar_url, :author_external_id, :
|
|
296
|
-
:like_count, :is_hidden, :permalink,
|
|
297
|
-
:posted_at, :created_at, :replies
|
|
341
|
+
:author_avatar_url, :author_external_id, :metadata,
|
|
342
|
+
:parent_external_id, :like_count, :is_hidden, :permalink,
|
|
343
|
+
:platform_data, :attachments, :posted_at, :created_at, :replies
|
|
298
344
|
|
|
299
345
|
def initialize(**attrs)
|
|
300
346
|
@external_id = nil
|
|
301
347
|
@author_avatar_url = nil
|
|
302
348
|
@author_external_id = nil
|
|
349
|
+
@metadata = nil
|
|
303
350
|
@parent_external_id = nil
|
|
304
351
|
@like_count = 0
|
|
305
352
|
@is_hidden = false
|
|
306
353
|
@permalink = nil
|
|
307
354
|
@platform_data = nil
|
|
355
|
+
@attachments = []
|
|
308
356
|
@replies = []
|
|
309
357
|
super
|
|
310
358
|
@posted_at = parse_time(@posted_at)
|
|
311
359
|
@created_at = parse_time(@created_at)
|
|
360
|
+
@attachments = (@attachments || []).map do |a|
|
|
361
|
+
a.is_a?(Attachment) ? a : Attachment.new(**a.transform_keys(&:to_sym))
|
|
362
|
+
end
|
|
312
363
|
@replies = (@replies || []).map do |r|
|
|
313
364
|
r.is_a?(Comment) ? r : Comment.new(**r.transform_keys(&:to_sym))
|
|
314
365
|
end
|
|
@@ -322,6 +373,112 @@ module PostProxy
|
|
|
322
373
|
end
|
|
323
374
|
end
|
|
324
375
|
|
|
376
|
+
class Chat < Model
|
|
377
|
+
attr_accessor :id, :profile_id, :platform, :participant_external_id,
|
|
378
|
+
:participant_username, :participant_name, :participant_avatar_url,
|
|
379
|
+
:external_conversation_id, :last_inbound_at, :last_outbound_at,
|
|
380
|
+
:last_message_at, :metadata, :archived, :created_at
|
|
381
|
+
|
|
382
|
+
def initialize(**attrs)
|
|
383
|
+
@participant_username = nil
|
|
384
|
+
@participant_name = nil
|
|
385
|
+
@participant_avatar_url = nil
|
|
386
|
+
@external_conversation_id = nil
|
|
387
|
+
@last_inbound_at = nil
|
|
388
|
+
@last_outbound_at = nil
|
|
389
|
+
@last_message_at = nil
|
|
390
|
+
@metadata = nil
|
|
391
|
+
@archived = nil
|
|
392
|
+
super
|
|
393
|
+
@last_inbound_at = parse_time(@last_inbound_at)
|
|
394
|
+
@last_outbound_at = parse_time(@last_outbound_at)
|
|
395
|
+
@last_message_at = parse_time(@last_message_at)
|
|
396
|
+
@created_at = parse_time(@created_at)
|
|
397
|
+
end
|
|
398
|
+
|
|
399
|
+
private
|
|
400
|
+
|
|
401
|
+
def parse_time(value)
|
|
402
|
+
return nil if value.nil?
|
|
403
|
+
value.is_a?(Time) ? value : Time.parse(value.to_s)
|
|
404
|
+
end
|
|
405
|
+
end
|
|
406
|
+
|
|
407
|
+
class Message < Model
|
|
408
|
+
attr_accessor :id, :chat_id, :external_id, :direction, :body, :status,
|
|
409
|
+
:tag, :external_comment_id, :error_message, :platform_data,
|
|
410
|
+
:external_posted_at, :external_delivered_at, :external_read_at,
|
|
411
|
+
:external_edited_at, :reply_to_external_id, :reply_markup,
|
|
412
|
+
:external_deleted_at, :reactions, :attachments,
|
|
413
|
+
:is_unsupported, :created_at
|
|
414
|
+
|
|
415
|
+
def initialize(**attrs)
|
|
416
|
+
@external_id = nil
|
|
417
|
+
@body = nil
|
|
418
|
+
@tag = nil
|
|
419
|
+
@external_comment_id = nil
|
|
420
|
+
@error_message = nil
|
|
421
|
+
@platform_data = nil
|
|
422
|
+
@external_posted_at = nil
|
|
423
|
+
@external_delivered_at = nil
|
|
424
|
+
@external_read_at = nil
|
|
425
|
+
@external_edited_at = nil
|
|
426
|
+
@reply_to_external_id = nil
|
|
427
|
+
@reply_markup = nil
|
|
428
|
+
@external_deleted_at = nil
|
|
429
|
+
@reactions = []
|
|
430
|
+
@attachments = []
|
|
431
|
+
@is_unsupported = false
|
|
432
|
+
super
|
|
433
|
+
@external_posted_at = parse_time(@external_posted_at)
|
|
434
|
+
@external_delivered_at = parse_time(@external_delivered_at)
|
|
435
|
+
@external_read_at = parse_time(@external_read_at)
|
|
436
|
+
@external_edited_at = parse_time(@external_edited_at)
|
|
437
|
+
@external_deleted_at = parse_time(@external_deleted_at)
|
|
438
|
+
@created_at = parse_time(@created_at)
|
|
439
|
+
@reactions = (@reactions || []).map do |r|
|
|
440
|
+
r.is_a?(Reaction) ? r : Reaction.new(**r.transform_keys(&:to_sym))
|
|
441
|
+
end
|
|
442
|
+
@attachments = (@attachments || []).map do |a|
|
|
443
|
+
a.is_a?(Attachment) ? a : Attachment.new(**a.transform_keys(&:to_sym))
|
|
444
|
+
end
|
|
445
|
+
end
|
|
446
|
+
|
|
447
|
+
private
|
|
448
|
+
|
|
449
|
+
def parse_time(value)
|
|
450
|
+
return nil if value.nil?
|
|
451
|
+
value.is_a?(Time) ? value : Time.parse(value.to_s)
|
|
452
|
+
end
|
|
453
|
+
end
|
|
454
|
+
|
|
455
|
+
class ProfileComment < Model
|
|
456
|
+
attr_accessor :id, :external_id, :parent_external_id, :placement_id,
|
|
457
|
+
:body, :status, :author_username, :author_avatar_url,
|
|
458
|
+
:platform_data, :posted_at, :created_at, :replies
|
|
459
|
+
|
|
460
|
+
def initialize(**attrs)
|
|
461
|
+
@parent_external_id = nil
|
|
462
|
+
@author_username = nil
|
|
463
|
+
@author_avatar_url = nil
|
|
464
|
+
@platform_data = nil
|
|
465
|
+
@replies = []
|
|
466
|
+
super
|
|
467
|
+
@posted_at = parse_time(@posted_at)
|
|
468
|
+
@created_at = parse_time(@created_at)
|
|
469
|
+
@replies = (@replies || []).map do |r|
|
|
470
|
+
r.is_a?(ProfileComment) ? r : ProfileComment.new(**r.transform_keys(&:to_sym))
|
|
471
|
+
end
|
|
472
|
+
end
|
|
473
|
+
|
|
474
|
+
private
|
|
475
|
+
|
|
476
|
+
def parse_time(value)
|
|
477
|
+
return nil if value.nil?
|
|
478
|
+
value.is_a?(Time) ? value : Time.parse(value.to_s)
|
|
479
|
+
end
|
|
480
|
+
end
|
|
481
|
+
|
|
325
482
|
class AcceptedResponse < Model
|
|
326
483
|
attr_accessor :accepted
|
|
327
484
|
end
|
|
@@ -452,11 +609,11 @@ module PostProxy
|
|
|
452
609
|
|
|
453
610
|
class PlatformParams < Model
|
|
454
611
|
attr_accessor :facebook, :instagram, :tiktok, :linkedin, :youtube,
|
|
455
|
-
:pinterest, :threads, :twitter, :bluesky, :telegram
|
|
612
|
+
:pinterest, :threads, :twitter, :bluesky, :telegram, :google_business
|
|
456
613
|
|
|
457
614
|
def to_h
|
|
458
615
|
result = {}
|
|
459
|
-
%i[facebook instagram tiktok linkedin youtube pinterest threads twitter bluesky telegram].each do |platform|
|
|
616
|
+
%i[facebook instagram tiktok linkedin youtube pinterest threads twitter bluesky telegram google_business].each do |platform|
|
|
460
617
|
value = send(platform)
|
|
461
618
|
next if value.nil?
|
|
462
619
|
|
data/lib/postproxy/version.rb
CHANGED
|
@@ -71,6 +71,35 @@ module PostProxy
|
|
|
71
71
|
:platform_data, :posted_at, :created_at
|
|
72
72
|
end
|
|
73
73
|
|
|
74
|
+
class MessageEventData < Model
|
|
75
|
+
attr_accessor :message
|
|
76
|
+
|
|
77
|
+
def initialize(**attrs)
|
|
78
|
+
@message = nil
|
|
79
|
+
super
|
|
80
|
+
@message = Message.new(**@message.transform_keys(&:to_sym)) if @message.is_a?(Hash)
|
|
81
|
+
end
|
|
82
|
+
end
|
|
83
|
+
|
|
84
|
+
class ReactionEventData < Model
|
|
85
|
+
attr_accessor :message, :sender_external_id, :action, :reaction, :emoji, :occurred_at
|
|
86
|
+
|
|
87
|
+
def initialize(**attrs)
|
|
88
|
+
@message = nil
|
|
89
|
+
@reaction = nil
|
|
90
|
+
@emoji = nil
|
|
91
|
+
@occurred_at = nil
|
|
92
|
+
super
|
|
93
|
+
@message = Message.new(**@message.transform_keys(&:to_sym)) if @message.is_a?(Hash)
|
|
94
|
+
end
|
|
95
|
+
end
|
|
96
|
+
|
|
97
|
+
class ProfileCommentCreatedData < Model
|
|
98
|
+
attr_accessor :id, :profile_id, :platform, :placement_id, :external_id,
|
|
99
|
+
:parent_external_id, :body, :status, :author_username,
|
|
100
|
+
:author_avatar_url, :platform_data, :posted_at, :created_at
|
|
101
|
+
end
|
|
102
|
+
|
|
74
103
|
DATA_CLASSES = {
|
|
75
104
|
"post.processed" => PostProcessedData,
|
|
76
105
|
"post.imported" => PostImportedData,
|
|
@@ -82,7 +111,17 @@ module PostProxy
|
|
|
82
111
|
"profile.disconnected" => ProfileEventData,
|
|
83
112
|
"profile.stats" => ProfileStatsData,
|
|
84
113
|
"media.failed" => MediaFailedData,
|
|
85
|
-
"comment.created" => CommentCreatedData
|
|
114
|
+
"comment.created" => CommentCreatedData,
|
|
115
|
+
"profile_comment.created" => ProfileCommentCreatedData,
|
|
116
|
+
"message.received" => MessageEventData,
|
|
117
|
+
"message.sent" => MessageEventData,
|
|
118
|
+
"message.delivered" => MessageEventData,
|
|
119
|
+
"message.read" => MessageEventData,
|
|
120
|
+
"message.edited" => MessageEventData,
|
|
121
|
+
"message.deleted" => MessageEventData,
|
|
122
|
+
"message.failed_waiting_for_retry" => MessageEventData,
|
|
123
|
+
"message.failed" => MessageEventData,
|
|
124
|
+
"reaction.received" => ReactionEventData
|
|
86
125
|
}.freeze
|
|
87
126
|
|
|
88
127
|
# Parse a webhook body and return a typed Event. `data` is parsed into the
|
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.10.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- PostProxy
|
|
@@ -78,8 +78,11 @@ files:
|
|
|
78
78
|
- lib/postproxy/client.rb
|
|
79
79
|
- lib/postproxy/constants.rb
|
|
80
80
|
- lib/postproxy/errors.rb
|
|
81
|
+
- lib/postproxy/resources/chats.rb
|
|
81
82
|
- lib/postproxy/resources/comments.rb
|
|
83
|
+
- lib/postproxy/resources/messages.rb
|
|
82
84
|
- lib/postproxy/resources/posts.rb
|
|
85
|
+
- lib/postproxy/resources/profile_comments.rb
|
|
83
86
|
- lib/postproxy/resources/profile_groups.rb
|
|
84
87
|
- lib/postproxy/resources/profiles.rb
|
|
85
88
|
- lib/postproxy/resources/queues.rb
|