postproxy-sdk 1.12.0 → 1.13.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 +67 -0
- data/lib/postproxy/resources/messages.rb +19 -2
- data/lib/postproxy/types.rb +101 -0
- data/lib/postproxy/version.rb +1 -1
- metadata +1 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: a6be6a31464b3afaf473473d94fd639b2553782cf0494e87ccfebb3e5e2e5e4a
|
|
4
|
+
data.tar.gz: 0ff052e8410c536e2938cf6887cecee713f615afa11207c88f733c29b6770d07
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 2a67b96326d47a4c33463c10b5346ff28debf6e4faf7ef11a297fded79abf9cac4b062099cc1e1cf6002e6e9f2bc4b9e641dbf26537552f8d2041310598a8163
|
|
7
|
+
data.tar.gz: cf8c37298d421c37886440e75819a9a1462d34ccadad79e4623cbb2cb431292e4ead2e1ba0014bd13b522c9c0e62737eeb524987bde5a97f99404e3416686950
|
data/README.md
CHANGED
|
@@ -464,6 +464,73 @@ message = client.comments.private_reply("post-id", "comment-id", profile_id: "pr
|
|
|
464
464
|
puts message.chat_id, message.status
|
|
465
465
|
```
|
|
466
466
|
|
|
467
|
+
### Quick replies and buttons (Facebook & Instagram)
|
|
468
|
+
|
|
469
|
+
Meta's two interactive primitives. **Quick replies** are chips above the participant's
|
|
470
|
+
composer that disappear once tapped; **buttons** are attached to the message and stay in
|
|
471
|
+
the thread. Telegram's equivalent is `reply_markup` above — passing `quick_replies` or
|
|
472
|
+
`buttons` on a Telegram or Bluesky chat returns `422`.
|
|
473
|
+
|
|
474
|
+
Each param accepts model instances or plain hashes, whichever you prefer:
|
|
475
|
+
|
|
476
|
+
```ruby
|
|
477
|
+
# Quick replies — up to 13. title ≤ 20 chars, payload ≤ 1000.
|
|
478
|
+
client.messages.send(
|
|
479
|
+
chat.id,
|
|
480
|
+
body: "What can I help with?",
|
|
481
|
+
quick_replies: [
|
|
482
|
+
PostProxy::QuickReply.new(title: "Track order", payload: "TRACK"),
|
|
483
|
+
{ title: "Talk to support", payload: "HELP" }
|
|
484
|
+
]
|
|
485
|
+
)
|
|
486
|
+
|
|
487
|
+
# Buttons — up to 3, each either web_url or postback. card is optional and
|
|
488
|
+
# requires buttons.
|
|
489
|
+
client.messages.send(
|
|
490
|
+
chat.id,
|
|
491
|
+
body: "Your order shipped",
|
|
492
|
+
buttons: [
|
|
493
|
+
PostProxy::MessageButton.new(type: "web_url", title: "Track", url: "https://shop.example.com/o/123"),
|
|
494
|
+
PostProxy::MessageButton.new(type: "postback", title: "Cancel", payload: "CANCEL:123")
|
|
495
|
+
],
|
|
496
|
+
card: PostProxy::MessageCard.new(
|
|
497
|
+
subtitle: "Arriving Friday",
|
|
498
|
+
image_url: "https://cdn.example.com/shoe.png",
|
|
499
|
+
default_action: { type: "web_url", url: "https://shop.example.com/o/123" }
|
|
500
|
+
)
|
|
501
|
+
)
|
|
502
|
+
```
|
|
503
|
+
|
|
504
|
+
Buttons are delivered as a Meta generic template and your `body` becomes the template's
|
|
505
|
+
element title — so **`body` is capped at 80 characters when buttons are present**. That is
|
|
506
|
+
Meta's limit, not PostProxy's, and a longer body is rejected with a `422` naming the
|
|
507
|
+
length. Buttons cannot be combined with media. Instagram is stricter than Messenger: it
|
|
508
|
+
delivers quick replies only on a plain-text message, so `quick_replies` with media or with
|
|
509
|
+
`buttons` returns `422` on Instagram while both are accepted on Facebook.
|
|
510
|
+
|
|
511
|
+
Validation happens server-side and names the offending index — `buttons[1].url must be an
|
|
512
|
+
https:// URL` — surfacing as the SDK's usual error for a `422`.
|
|
513
|
+
|
|
514
|
+
> The new params are sent on the JSON path only. To combine quick replies with an
|
|
515
|
+
> attachment, pass `media` as a hosted URL rather than uploading via `media_files`.
|
|
516
|
+
|
|
517
|
+
A tap comes back as an **inbound message** carrying `tapped_action`:
|
|
518
|
+
|
|
519
|
+
```ruby
|
|
520
|
+
inbound = client.messages.list(chat.id, direction: "inbound")
|
|
521
|
+
inbound.data.each do |msg|
|
|
522
|
+
next unless msg.tapped_action
|
|
523
|
+
|
|
524
|
+
# kind: "quick_reply", "postback", or "callback_query"
|
|
525
|
+
puts "#{msg.tapped_action.kind}: #{msg.tapped_action.payload}"
|
|
526
|
+
end
|
|
527
|
+
```
|
|
528
|
+
|
|
529
|
+
Subscribe to `message.received` to react to taps as they happen — the same field is on the
|
|
530
|
+
webhook payload. `tapped_action` is derived rather than stored, so it also resolves for
|
|
531
|
+
taps recorded before PostProxy exposed it, including Instagram ice-breaker taps and
|
|
532
|
+
Telegram callback queries (`kind` `"callback_query"`). A tap also opens the 24h window.
|
|
533
|
+
|
|
467
534
|
## Profile comments (Google Business reviews)
|
|
468
535
|
|
|
469
536
|
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.
|
|
@@ -22,9 +22,16 @@ module PostProxy
|
|
|
22
22
|
)
|
|
23
23
|
end
|
|
24
24
|
|
|
25
|
+
# quick_replies, buttons, and card are Facebook and Instagram only — they
|
|
26
|
+
# return 422 on Telegram and Bluesky, where reply_markup is the
|
|
27
|
+
# equivalent. They are sent on the JSON path only, so pass media as hosted
|
|
28
|
+
# URLs rather than media_files when combining with an attachment.
|
|
29
|
+
#
|
|
30
|
+
# Each accepts model instances or plain hashes.
|
|
25
31
|
def send_message(chat_id, body: nil, media: nil, media_files: nil, tag: nil,
|
|
26
|
-
reply_to_external_id: nil, reply_markup: nil,
|
|
27
|
-
|
|
32
|
+
reply_to_external_id: nil, reply_markup: nil,
|
|
33
|
+
quick_replies: nil, buttons: nil, card: nil,
|
|
34
|
+
profile_group_id: nil, idempotency_key: nil)
|
|
28
35
|
has_files = media_files && !media_files.empty?
|
|
29
36
|
|
|
30
37
|
if has_files
|
|
@@ -58,6 +65,9 @@ module PostProxy
|
|
|
58
65
|
json_body[:tag] = tag if tag
|
|
59
66
|
json_body[:reply_to_external_id] = reply_to_external_id if reply_to_external_id
|
|
60
67
|
json_body[:reply_markup] = reply_markup if reply_markup
|
|
68
|
+
json_body[:quick_replies] = quick_replies.map { |q| serialize_interactive(q) } if quick_replies
|
|
69
|
+
json_body[:buttons] = buttons.map { |b| serialize_interactive(b) } if buttons
|
|
70
|
+
json_body[:card] = serialize_interactive(card) if card
|
|
61
71
|
|
|
62
72
|
result = @client.request(:post, "/chats/#{chat_id}/messages",
|
|
63
73
|
json: json_body,
|
|
@@ -111,6 +121,13 @@ module PostProxy
|
|
|
111
121
|
|
|
112
122
|
private
|
|
113
123
|
|
|
124
|
+
# Interactive params accept model instances or plain hashes. Models expose
|
|
125
|
+
# to_h with nils dropped, so an omitted content_type stays omitted rather
|
|
126
|
+
# than being sent as null.
|
|
127
|
+
def serialize_interactive(value)
|
|
128
|
+
value.respond_to?(:to_h) && !value.is_a?(Hash) ? value.to_h : value
|
|
129
|
+
end
|
|
130
|
+
|
|
114
131
|
def mime_type_for(filename)
|
|
115
132
|
case File.extname(filename).downcase
|
|
116
133
|
when ".jpg", ".jpeg" then "image/jpeg"
|
data/lib/postproxy/types.rb
CHANGED
|
@@ -429,11 +429,94 @@ module PostProxy
|
|
|
429
429
|
end
|
|
430
430
|
end
|
|
431
431
|
|
|
432
|
+
# A tappable chip above the participant's composer, gone once tapped.
|
|
433
|
+
# Facebook and Instagram only; up to 13 per send. +content_type+ is optional
|
|
434
|
+
# on send (only "text" is accepted) and always present on responses.
|
|
435
|
+
class QuickReply < Model
|
|
436
|
+
attr_accessor :content_type, :title, :payload
|
|
437
|
+
|
|
438
|
+
def initialize(**attrs)
|
|
439
|
+
@content_type = nil
|
|
440
|
+
super
|
|
441
|
+
end
|
|
442
|
+
|
|
443
|
+
def to_h
|
|
444
|
+
{ content_type: @content_type, title: @title, payload: @payload }.compact
|
|
445
|
+
end
|
|
446
|
+
end
|
|
447
|
+
|
|
448
|
+
# A button attached to the message, delivered as a Meta generic template.
|
|
449
|
+
# Facebook and Instagram only; up to 3 per send. +url+ is required and must be
|
|
450
|
+
# https when +type+ is "web_url"; +payload+ is required when +type+ is
|
|
451
|
+
# "postback". +type+ is a plain string rather than an enum so a new Meta
|
|
452
|
+
# button type needs no SDK release.
|
|
453
|
+
class MessageButton < Model
|
|
454
|
+
attr_accessor :type, :title, :url, :payload
|
|
455
|
+
|
|
456
|
+
def initialize(**attrs)
|
|
457
|
+
@url = nil
|
|
458
|
+
@payload = nil
|
|
459
|
+
super
|
|
460
|
+
end
|
|
461
|
+
|
|
462
|
+
def to_h
|
|
463
|
+
{ type: @type, title: @title, url: @url, payload: @payload }.compact
|
|
464
|
+
end
|
|
465
|
+
end
|
|
466
|
+
|
|
467
|
+
class CardDefaultAction < Model
|
|
468
|
+
attr_accessor :type, :url
|
|
469
|
+
|
|
470
|
+
def to_h
|
|
471
|
+
{ type: @type, url: @url }.compact
|
|
472
|
+
end
|
|
473
|
+
end
|
|
474
|
+
|
|
475
|
+
# Extra fields for the generic-template element that carries +buttons+.
|
|
476
|
+
# Requires +buttons+. +subtitle+ is capped at 80 characters, and both URLs
|
|
477
|
+
# must be https.
|
|
478
|
+
class MessageCard < Model
|
|
479
|
+
attr_accessor :subtitle, :image_url, :default_action
|
|
480
|
+
|
|
481
|
+
def initialize(**attrs)
|
|
482
|
+
@subtitle = nil
|
|
483
|
+
@image_url = nil
|
|
484
|
+
@default_action = nil
|
|
485
|
+
super
|
|
486
|
+
if @default_action && !@default_action.is_a?(CardDefaultAction)
|
|
487
|
+
@default_action = CardDefaultAction.new(**@default_action.transform_keys(&:to_sym))
|
|
488
|
+
end
|
|
489
|
+
end
|
|
490
|
+
|
|
491
|
+
def to_h
|
|
492
|
+
{
|
|
493
|
+
subtitle: @subtitle,
|
|
494
|
+
image_url: @image_url,
|
|
495
|
+
default_action: @default_action&.to_h
|
|
496
|
+
}.compact
|
|
497
|
+
end
|
|
498
|
+
end
|
|
499
|
+
|
|
500
|
+
# Set on inbound messages created by a tap on an element you sent. Derived
|
|
501
|
+
# from platform_data rather than stored, so it also resolves for taps ingested
|
|
502
|
+
# before PostProxy exposed this field. +kind+ is one of "quick_reply",
|
|
503
|
+
# "postback", or "callback_query" — the last is Telegram, so this is not
|
|
504
|
+
# Meta-only even though the send params are.
|
|
505
|
+
class TappedAction < Model
|
|
506
|
+
attr_accessor :kind, :payload, :title
|
|
507
|
+
|
|
508
|
+
def initialize(**attrs)
|
|
509
|
+
@title = nil
|
|
510
|
+
super
|
|
511
|
+
end
|
|
512
|
+
end
|
|
513
|
+
|
|
432
514
|
class Message < Model
|
|
433
515
|
attr_accessor :id, :chat_id, :external_id, :direction, :body, :status,
|
|
434
516
|
:tag, :external_comment_id, :error_message, :platform_data,
|
|
435
517
|
:external_posted_at, :external_delivered_at, :external_read_at,
|
|
436
518
|
:external_edited_at, :reply_to_external_id, :reply_markup,
|
|
519
|
+
:quick_replies, :buttons, :card, :tapped_action,
|
|
437
520
|
:external_deleted_at, :reactions, :attachments,
|
|
438
521
|
:is_unsupported, :created_at
|
|
439
522
|
|
|
@@ -450,6 +533,10 @@ module PostProxy
|
|
|
450
533
|
@external_edited_at = nil
|
|
451
534
|
@reply_to_external_id = nil
|
|
452
535
|
@reply_markup = nil
|
|
536
|
+
@quick_replies = nil
|
|
537
|
+
@buttons = nil
|
|
538
|
+
@card = nil
|
|
539
|
+
@tapped_action = nil
|
|
453
540
|
@external_deleted_at = nil
|
|
454
541
|
@reactions = []
|
|
455
542
|
@attachments = []
|
|
@@ -467,6 +554,20 @@ module PostProxy
|
|
|
467
554
|
@attachments = (@attachments || []).map do |a|
|
|
468
555
|
a.is_a?(Attachment) ? a : Attachment.new(**a.transform_keys(&:to_sym))
|
|
469
556
|
end
|
|
557
|
+
# Left nil rather than [] when absent — the API omits these on non-Meta
|
|
558
|
+
# networks, and an empty array would read as "sent with none".
|
|
559
|
+
@quick_replies = @quick_replies&.map do |q|
|
|
560
|
+
q.is_a?(QuickReply) ? q : QuickReply.new(**q.transform_keys(&:to_sym))
|
|
561
|
+
end
|
|
562
|
+
@buttons = @buttons&.map do |b|
|
|
563
|
+
b.is_a?(MessageButton) ? b : MessageButton.new(**b.transform_keys(&:to_sym))
|
|
564
|
+
end
|
|
565
|
+
if @card && !@card.is_a?(MessageCard)
|
|
566
|
+
@card = MessageCard.new(**@card.transform_keys(&:to_sym))
|
|
567
|
+
end
|
|
568
|
+
if @tapped_action && !@tapped_action.is_a?(TappedAction)
|
|
569
|
+
@tapped_action = TappedAction.new(**@tapped_action.transform_keys(&:to_sym))
|
|
570
|
+
end
|
|
470
571
|
end
|
|
471
572
|
|
|
472
573
|
private
|
data/lib/postproxy/version.rb
CHANGED