postproxy-sdk 1.9.0 → 1.11.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 +98 -1
- data/lib/postproxy/client.rb +12 -0
- data/lib/postproxy/constants.rb +14 -0
- 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/profiles.rb +35 -0
- data/lib/postproxy/types.rb +142 -5
- data/lib/postproxy/version.rb +1 -1
- data/lib/postproxy/webhook_events.rb +40 -1
- 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: c459486395f3eed2f4199f1946ed7a71c7b6b5de5ae270e46b088e779c772459
|
|
4
|
+
data.tar.gz: 38788f6fd45363f0c229af3cd7fd28a79a21511d1a7b8c4c417bb2d0c5c93f48
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 8458cc1ca2c4bd873fe980d9b18db5d81b2871c3d027f914a0555ce686cbca1daba5400464f2a950ceb2ba3602657d6e2bf1daa5d5d23c891addbdc0c5b2c54b
|
|
7
|
+
data.tar.gz: 3b9fa99981232895852351741ebdaf2cf5675d585b298100bb29ae1fdca054f67b6e16d9e909ba1db6a15f2fda1b0b06a69f37f6443eceb2680af253e0ca27a3
|
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,71 @@ 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
|
|
318
394
|
```
|
|
319
395
|
|
|
320
396
|
## Profile comments (Google Business reviews)
|
|
@@ -355,6 +431,24 @@ profile = client.profiles.get("prof-id")
|
|
|
355
431
|
# Get placements for a profile
|
|
356
432
|
placements = client.profiles.placements("prof-id").data
|
|
357
433
|
|
|
434
|
+
# Move a placement (e.g. a Facebook Page or Telegram channel) to another group
|
|
435
|
+
placement = client.profiles.assign_placement_to_group("prof-id",
|
|
436
|
+
placement_id: "placement-external-id",
|
|
437
|
+
target_profile_group_id: "pg-other"
|
|
438
|
+
)
|
|
439
|
+
puts placement.profile_group_id # => "pg-other"
|
|
440
|
+
|
|
441
|
+
# Ice breakers (Instagram DMs): FAQ prompts shown when a user opens a chat
|
|
442
|
+
result = client.profiles.ice_breakers("prof-id")
|
|
443
|
+
puts result.ice_breakers.map(&:question)
|
|
444
|
+
|
|
445
|
+
client.profiles.set_ice_breakers("prof-id", [
|
|
446
|
+
{ question: "What services do you offer?", payload: "services" },
|
|
447
|
+
{ question: "What are your hours?", payload: "hours" }
|
|
448
|
+
]) # 1-4 items
|
|
449
|
+
|
|
450
|
+
client.profiles.delete_ice_breakers("prof-id")
|
|
451
|
+
|
|
358
452
|
# Delete a profile
|
|
359
453
|
client.profiles.delete("prof-id")
|
|
360
454
|
|
|
@@ -445,6 +539,9 @@ platforms = PostProxy::PlatformParams.new(
|
|
|
445
539
|
board_id: "board-123"
|
|
446
540
|
),
|
|
447
541
|
threads: PostProxy::ThreadsParams.new(format: "post"),
|
|
542
|
+
# Twitter also supports polls:
|
|
543
|
+
# twitter: PostProxy::TwitterParams.new(format: "poll",
|
|
544
|
+
# poll_options: ["Yes", "No"], poll_duration_minutes: 1440),
|
|
448
545
|
twitter: PostProxy::TwitterParams.new(format: "post"),
|
|
449
546
|
bluesky: PostProxy::BlueskyParams.new(format: "post"),
|
|
450
547
|
telegram: PostProxy::TelegramParams.new(
|
data/lib/postproxy/client.rb
CHANGED
|
@@ -8,6 +8,8 @@ require_relative "resources/webhooks"
|
|
|
8
8
|
require_relative "resources/queues"
|
|
9
9
|
require_relative "resources/comments"
|
|
10
10
|
require_relative "resources/profile_comments"
|
|
11
|
+
require_relative "resources/chats"
|
|
12
|
+
require_relative "resources/messages"
|
|
11
13
|
|
|
12
14
|
module PostProxy
|
|
13
15
|
class Client
|
|
@@ -25,6 +27,8 @@ module PostProxy
|
|
|
25
27
|
@queues = nil
|
|
26
28
|
@comments = nil
|
|
27
29
|
@profile_comments = nil
|
|
30
|
+
@chats = nil
|
|
31
|
+
@messages = nil
|
|
28
32
|
end
|
|
29
33
|
|
|
30
34
|
def posts
|
|
@@ -55,6 +59,14 @@ module PostProxy
|
|
|
55
59
|
@profile_comments ||= Resources::ProfileComments.new(self)
|
|
56
60
|
end
|
|
57
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
|
+
|
|
58
70
|
def request(method, path, params: nil, json: nil, data: nil, files: nil, profile_group_id: nil)
|
|
59
71
|
url = "/api#{path}"
|
|
60
72
|
|
data/lib/postproxy/constants.rb
CHANGED
|
@@ -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
|
|
@@ -36,6 +36,41 @@ module PostProxy
|
|
|
36
36
|
ProfileStatsResponse.new(data: result[:data])
|
|
37
37
|
end
|
|
38
38
|
|
|
39
|
+
# Moves a placement (e.g. a Facebook Page or Telegram channel) to another
|
|
40
|
+
# profile group. `placement_id` is the placement's external ID as
|
|
41
|
+
# returned by #placements.
|
|
42
|
+
def assign_placement_to_group(id, placement_id:, target_profile_group_id:, profile_group_id: nil)
|
|
43
|
+
result = @client.request(:patch, "/profiles/#{id}/assign_placement_to_group",
|
|
44
|
+
json: {
|
|
45
|
+
placement_id: placement_id,
|
|
46
|
+
target_profile_group_id: target_profile_group_id
|
|
47
|
+
},
|
|
48
|
+
profile_group_id: profile_group_id
|
|
49
|
+
)
|
|
50
|
+
Placement.new(**result)
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
# Lists DM ice breakers. Supported for Instagram profiles only.
|
|
54
|
+
def ice_breakers(id, profile_group_id: nil)
|
|
55
|
+
result = @client.request(:get, "/profiles/#{id}/ice_breakers", profile_group_id: profile_group_id)
|
|
56
|
+
IceBreakersResponse.new(**result)
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
# Replaces the DM ice breakers for a profile (1-4 items).
|
|
60
|
+
def set_ice_breakers(id, ice_breakers, profile_group_id: nil)
|
|
61
|
+
items = ice_breakers.map { |ib| ib.is_a?(IceBreaker) ? ib.to_h : ib }
|
|
62
|
+
result = @client.request(:post, "/profiles/#{id}/ice_breakers",
|
|
63
|
+
json: { ice_breakers: items },
|
|
64
|
+
profile_group_id: profile_group_id
|
|
65
|
+
)
|
|
66
|
+
SuccessResponse.new(**result)
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
def delete_ice_breakers(id, profile_group_id: nil)
|
|
70
|
+
result = @client.request(:delete, "/profiles/#{id}/ice_breakers", profile_group_id: profile_group_id)
|
|
71
|
+
SuccessResponse.new(**result)
|
|
72
|
+
end
|
|
73
|
+
|
|
39
74
|
def delete(id, profile_group_id: nil)
|
|
40
75
|
result = @client.request(:delete, "/profiles/#{id}", profile_group_id: profile_group_id)
|
|
41
76
|
SuccessResponse.new(**result)
|
data/lib/postproxy/types.rb
CHANGED
|
@@ -235,7 +235,29 @@ module PostProxy
|
|
|
235
235
|
end
|
|
236
236
|
|
|
237
237
|
class Placement < Model
|
|
238
|
-
|
|
238
|
+
# `metadata` and `profile_group_id` are present in placements and
|
|
239
|
+
# assign_placement_to_group responses.
|
|
240
|
+
attr_accessor :id, :name, :metadata, :profile_group_id
|
|
241
|
+
end
|
|
242
|
+
|
|
243
|
+
# Instagram DM ice breaker (FAQ prompt). May carry extra platform fields.
|
|
244
|
+
class IceBreaker < Model
|
|
245
|
+
attr_accessor :question, :payload
|
|
246
|
+
|
|
247
|
+
def to_h
|
|
248
|
+
{ question: question, payload: payload }
|
|
249
|
+
end
|
|
250
|
+
end
|
|
251
|
+
|
|
252
|
+
class IceBreakersResponse < Model
|
|
253
|
+
attr_accessor :ice_breakers
|
|
254
|
+
|
|
255
|
+
def initialize(**attrs)
|
|
256
|
+
@ice_breakers = (attrs.delete(:ice_breakers) || []).map do |ib|
|
|
257
|
+
ib.is_a?(IceBreaker) ? ib : IceBreaker.new(**ib)
|
|
258
|
+
end
|
|
259
|
+
super
|
|
260
|
+
end
|
|
239
261
|
end
|
|
240
262
|
|
|
241
263
|
class StatsRecord < Model
|
|
@@ -307,25 +329,59 @@ module PostProxy
|
|
|
307
329
|
end
|
|
308
330
|
end
|
|
309
331
|
|
|
332
|
+
class Attachment < Model
|
|
333
|
+
attr_accessor :id, :type, :url, :status, :external_id
|
|
334
|
+
|
|
335
|
+
def initialize(**attrs)
|
|
336
|
+
@url = nil
|
|
337
|
+
@external_id = nil
|
|
338
|
+
super
|
|
339
|
+
end
|
|
340
|
+
end
|
|
341
|
+
|
|
342
|
+
class Reaction < Model
|
|
343
|
+
attr_accessor :sender_external_id, :emoji, :reaction, :at
|
|
344
|
+
|
|
345
|
+
def initialize(**attrs)
|
|
346
|
+
@emoji = nil
|
|
347
|
+
@reaction = nil
|
|
348
|
+
@at = nil
|
|
349
|
+
super
|
|
350
|
+
@at = parse_time(@at)
|
|
351
|
+
end
|
|
352
|
+
|
|
353
|
+
private
|
|
354
|
+
|
|
355
|
+
def parse_time(value)
|
|
356
|
+
return nil if value.nil?
|
|
357
|
+
value.is_a?(Time) ? value : Time.parse(value.to_s)
|
|
358
|
+
end
|
|
359
|
+
end
|
|
360
|
+
|
|
310
361
|
class Comment < Model
|
|
311
362
|
attr_accessor :id, :external_id, :body, :status, :author_username,
|
|
312
|
-
:author_avatar_url, :author_external_id, :
|
|
313
|
-
:like_count, :is_hidden, :permalink,
|
|
314
|
-
:posted_at, :created_at, :replies
|
|
363
|
+
:author_avatar_url, :author_external_id, :metadata,
|
|
364
|
+
:parent_external_id, :like_count, :is_hidden, :permalink,
|
|
365
|
+
:platform_data, :attachments, :posted_at, :created_at, :replies
|
|
315
366
|
|
|
316
367
|
def initialize(**attrs)
|
|
317
368
|
@external_id = nil
|
|
318
369
|
@author_avatar_url = nil
|
|
319
370
|
@author_external_id = nil
|
|
371
|
+
@metadata = nil
|
|
320
372
|
@parent_external_id = nil
|
|
321
373
|
@like_count = 0
|
|
322
374
|
@is_hidden = false
|
|
323
375
|
@permalink = nil
|
|
324
376
|
@platform_data = nil
|
|
377
|
+
@attachments = []
|
|
325
378
|
@replies = []
|
|
326
379
|
super
|
|
327
380
|
@posted_at = parse_time(@posted_at)
|
|
328
381
|
@created_at = parse_time(@created_at)
|
|
382
|
+
@attachments = (@attachments || []).map do |a|
|
|
383
|
+
a.is_a?(Attachment) ? a : Attachment.new(**a.transform_keys(&:to_sym))
|
|
384
|
+
end
|
|
329
385
|
@replies = (@replies || []).map do |r|
|
|
330
386
|
r.is_a?(Comment) ? r : Comment.new(**r.transform_keys(&:to_sym))
|
|
331
387
|
end
|
|
@@ -339,6 +395,85 @@ module PostProxy
|
|
|
339
395
|
end
|
|
340
396
|
end
|
|
341
397
|
|
|
398
|
+
class Chat < Model
|
|
399
|
+
attr_accessor :id, :profile_id, :platform, :participant_external_id,
|
|
400
|
+
:participant_username, :participant_name, :participant_avatar_url,
|
|
401
|
+
:external_conversation_id, :last_inbound_at, :last_outbound_at,
|
|
402
|
+
:last_message_at, :metadata, :archived, :created_at
|
|
403
|
+
|
|
404
|
+
def initialize(**attrs)
|
|
405
|
+
@participant_username = nil
|
|
406
|
+
@participant_name = nil
|
|
407
|
+
@participant_avatar_url = nil
|
|
408
|
+
@external_conversation_id = nil
|
|
409
|
+
@last_inbound_at = nil
|
|
410
|
+
@last_outbound_at = nil
|
|
411
|
+
@last_message_at = nil
|
|
412
|
+
@metadata = nil
|
|
413
|
+
@archived = nil
|
|
414
|
+
super
|
|
415
|
+
@last_inbound_at = parse_time(@last_inbound_at)
|
|
416
|
+
@last_outbound_at = parse_time(@last_outbound_at)
|
|
417
|
+
@last_message_at = parse_time(@last_message_at)
|
|
418
|
+
@created_at = parse_time(@created_at)
|
|
419
|
+
end
|
|
420
|
+
|
|
421
|
+
private
|
|
422
|
+
|
|
423
|
+
def parse_time(value)
|
|
424
|
+
return nil if value.nil?
|
|
425
|
+
value.is_a?(Time) ? value : Time.parse(value.to_s)
|
|
426
|
+
end
|
|
427
|
+
end
|
|
428
|
+
|
|
429
|
+
class Message < Model
|
|
430
|
+
attr_accessor :id, :chat_id, :external_id, :direction, :body, :status,
|
|
431
|
+
:tag, :external_comment_id, :error_message, :platform_data,
|
|
432
|
+
:external_posted_at, :external_delivered_at, :external_read_at,
|
|
433
|
+
:external_edited_at, :reply_to_external_id, :reply_markup,
|
|
434
|
+
:external_deleted_at, :reactions, :attachments,
|
|
435
|
+
:is_unsupported, :created_at
|
|
436
|
+
|
|
437
|
+
def initialize(**attrs)
|
|
438
|
+
@external_id = nil
|
|
439
|
+
@body = nil
|
|
440
|
+
@tag = nil
|
|
441
|
+
@external_comment_id = nil
|
|
442
|
+
@error_message = nil
|
|
443
|
+
@platform_data = nil
|
|
444
|
+
@external_posted_at = nil
|
|
445
|
+
@external_delivered_at = nil
|
|
446
|
+
@external_read_at = nil
|
|
447
|
+
@external_edited_at = nil
|
|
448
|
+
@reply_to_external_id = nil
|
|
449
|
+
@reply_markup = nil
|
|
450
|
+
@external_deleted_at = nil
|
|
451
|
+
@reactions = []
|
|
452
|
+
@attachments = []
|
|
453
|
+
@is_unsupported = false
|
|
454
|
+
super
|
|
455
|
+
@external_posted_at = parse_time(@external_posted_at)
|
|
456
|
+
@external_delivered_at = parse_time(@external_delivered_at)
|
|
457
|
+
@external_read_at = parse_time(@external_read_at)
|
|
458
|
+
@external_edited_at = parse_time(@external_edited_at)
|
|
459
|
+
@external_deleted_at = parse_time(@external_deleted_at)
|
|
460
|
+
@created_at = parse_time(@created_at)
|
|
461
|
+
@reactions = (@reactions || []).map do |r|
|
|
462
|
+
r.is_a?(Reaction) ? r : Reaction.new(**r.transform_keys(&:to_sym))
|
|
463
|
+
end
|
|
464
|
+
@attachments = (@attachments || []).map do |a|
|
|
465
|
+
a.is_a?(Attachment) ? a : Attachment.new(**a.transform_keys(&:to_sym))
|
|
466
|
+
end
|
|
467
|
+
end
|
|
468
|
+
|
|
469
|
+
private
|
|
470
|
+
|
|
471
|
+
def parse_time(value)
|
|
472
|
+
return nil if value.nil?
|
|
473
|
+
value.is_a?(Time) ? value : Time.parse(value.to_s)
|
|
474
|
+
end
|
|
475
|
+
end
|
|
476
|
+
|
|
342
477
|
class ProfileComment < Model
|
|
343
478
|
attr_accessor :id, :external_id, :parent_external_id, :placement_id,
|
|
344
479
|
:body, :status, :author_username, :author_avatar_url,
|
|
@@ -483,7 +618,9 @@ module PostProxy
|
|
|
483
618
|
end
|
|
484
619
|
|
|
485
620
|
class TwitterParams < Model
|
|
486
|
-
|
|
621
|
+
# poll_options and poll_duration_minutes are required when format is
|
|
622
|
+
# "poll": 2-4 options (max 25 chars each), duration 5 to 10080 minutes.
|
|
623
|
+
attr_accessor :format, :poll_options, :poll_duration_minutes
|
|
487
624
|
end
|
|
488
625
|
|
|
489
626
|
class BlueskyParams < Model
|
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.11.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- PostProxy
|
|
@@ -78,7 +78,9 @@ 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
|
|
83
85
|
- lib/postproxy/resources/profile_comments.rb
|
|
84
86
|
- lib/postproxy/resources/profile_groups.rb
|