postproxy-sdk 1.12.0 → 1.14.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 +480 -9
- data/lib/postproxy/client.rb +72 -7
- data/lib/postproxy/constants.rb +37 -2
- data/lib/postproxy/errors.rb +15 -0
- data/lib/postproxy/resources/api_keys.rb +30 -0
- data/lib/postproxy/resources/chats.rb +30 -1
- data/lib/postproxy/resources/comments.rb +52 -4
- data/lib/postproxy/resources/google_business.rb +194 -0
- data/lib/postproxy/resources/messages.rb +43 -2
- data/lib/postproxy/resources/placements.rb +35 -0
- data/lib/postproxy/resources/posts.rb +86 -25
- data/lib/postproxy/resources/profile_groups.rb +8 -1
- data/lib/postproxy/resources/profiles.rb +131 -0
- data/lib/postproxy/resources/whatsapp.rb +278 -0
- data/lib/postproxy/resources/whatsapp_templates.rb +108 -0
- data/lib/postproxy/types.rb +508 -28
- data/lib/postproxy/version.rb +1 -1
- data/lib/postproxy/webhook_events.rb +90 -4
- metadata +9 -4
data/lib/postproxy/types.rb
CHANGED
|
@@ -19,11 +19,22 @@ module PostProxy
|
|
|
19
19
|
end
|
|
20
20
|
|
|
21
21
|
class Profile < Model
|
|
22
|
-
|
|
22
|
+
# `inactive_reason` says why an "inactive" profile was deactivated —
|
|
23
|
+
# "network_taken" means the same account was connected elsewhere.
|
|
24
|
+
# `platform_id` is the account's id on the platform.
|
|
25
|
+
attr_accessor :id, :name, :username, :status, :inactive_reason, :platform,
|
|
26
|
+
:platform_id, :profile_group_id, :expires_at, :post_count,
|
|
27
|
+
:avatar_url, :platform_url, :placements_sync_status
|
|
23
28
|
|
|
24
29
|
def initialize(**attrs)
|
|
30
|
+
@username = nil
|
|
31
|
+
@inactive_reason = nil
|
|
32
|
+
@platform_id = nil
|
|
25
33
|
@expires_at = nil
|
|
26
34
|
@post_count = 0
|
|
35
|
+
@avatar_url = nil
|
|
36
|
+
@platform_url = nil
|
|
37
|
+
@placements_sync_status = nil
|
|
27
38
|
super
|
|
28
39
|
@expires_at = parse_time(@expires_at)
|
|
29
40
|
end
|
|
@@ -75,19 +86,39 @@ module PostProxy
|
|
|
75
86
|
end
|
|
76
87
|
end
|
|
77
88
|
|
|
89
|
+
# A file PostProxy generated for one platform, e.g. a TikTok cover.
|
|
90
|
+
class PlatformAttachment < Model
|
|
91
|
+
attr_accessor :id, :type, :status, :content_type, :url
|
|
92
|
+
|
|
93
|
+
def initialize(**attrs)
|
|
94
|
+
@content_type = nil
|
|
95
|
+
@url = nil
|
|
96
|
+
super
|
|
97
|
+
end
|
|
98
|
+
end
|
|
99
|
+
|
|
78
100
|
class PlatformResult < Model
|
|
79
|
-
attr_accessor :platform, :
|
|
101
|
+
attr_accessor :platform, :profile_id, :profile_name, :status, :permalink,
|
|
102
|
+
:params, :error, :error_details, :attempted_at, :insights,
|
|
103
|
+
:attachments
|
|
80
104
|
|
|
81
105
|
def initialize(**attrs)
|
|
106
|
+
@profile_id = nil
|
|
107
|
+
@profile_name = nil
|
|
108
|
+
@permalink = nil
|
|
82
109
|
@params = nil
|
|
83
110
|
@error = nil
|
|
84
111
|
@error_details = nil
|
|
85
112
|
@attempted_at = nil
|
|
86
113
|
@insights = nil
|
|
114
|
+
@attachments = nil
|
|
87
115
|
super
|
|
88
116
|
@attempted_at = parse_time(@attempted_at)
|
|
89
117
|
@insights = Insights.new(**@insights) if @insights.is_a?(Hash)
|
|
90
118
|
@error_details = ErrorDetails.new(**@error_details.transform_keys(&:to_sym)) if @error_details.is_a?(Hash)
|
|
119
|
+
@attachments = @attachments&.map do |a|
|
|
120
|
+
a.is_a?(PlatformAttachment) ? a : PlatformAttachment.new(**a.transform_keys(&:to_sym))
|
|
121
|
+
end
|
|
91
122
|
end
|
|
92
123
|
|
|
93
124
|
private
|
|
@@ -127,14 +158,20 @@ module PostProxy
|
|
|
127
158
|
end
|
|
128
159
|
|
|
129
160
|
class ThreadChild < Model
|
|
130
|
-
|
|
161
|
+
# Each thread item is delivered separately; `platforms` shows whether it
|
|
162
|
+
# went out.
|
|
163
|
+
attr_accessor :id, :body, :media, :platforms
|
|
131
164
|
|
|
132
165
|
def initialize(**attrs)
|
|
133
166
|
@media = []
|
|
167
|
+
@platforms = []
|
|
134
168
|
super
|
|
135
169
|
@media = (@media || []).map do |m|
|
|
136
170
|
m.is_a?(Media) ? m : Media.new(**m.transform_keys(&:to_sym))
|
|
137
171
|
end
|
|
172
|
+
@platforms = (@platforms || []).map do |p|
|
|
173
|
+
p.is_a?(PlatformResult) ? p : PlatformResult.new(**p.transform_keys(&:to_sym))
|
|
174
|
+
end
|
|
138
175
|
end
|
|
139
176
|
end
|
|
140
177
|
|
|
@@ -162,10 +199,13 @@ module PostProxy
|
|
|
162
199
|
end
|
|
163
200
|
|
|
164
201
|
class Post < Model
|
|
202
|
+
# `source` is "postproxy" or "imported".
|
|
165
203
|
attr_accessor :id, :body, :status, :scheduled_at, :created_at, :media, :platforms, :thread,
|
|
166
|
-
:queue_id, :queue_priority
|
|
204
|
+
:queue_id, :queue_priority, :draft, :source
|
|
167
205
|
|
|
168
206
|
def initialize(**attrs)
|
|
207
|
+
@draft = nil
|
|
208
|
+
@source = nil
|
|
169
209
|
@scheduled_at = nil
|
|
170
210
|
@media = []
|
|
171
211
|
@platforms = []
|
|
@@ -237,7 +277,53 @@ module PostProxy
|
|
|
237
277
|
class Placement < Model
|
|
238
278
|
# `metadata` and `profile_group_id` are present in placements and
|
|
239
279
|
# assign_placement_to_group responses.
|
|
240
|
-
attr_accessor :id, :name, :metadata, :profile_group_id
|
|
280
|
+
attr_accessor :id, :name, :avatar_url, :metadata, :profile_group_id
|
|
281
|
+
end
|
|
282
|
+
|
|
283
|
+
class AccountPlacementProfile < Model
|
|
284
|
+
attr_accessor :id, :name, :connected
|
|
285
|
+
end
|
|
286
|
+
|
|
287
|
+
# Account-level placement, returned by Placements#list. A placement (a
|
|
288
|
+
# Facebook Page, LinkedIn organization, WhatsApp number, ...) can be
|
|
289
|
+
# reachable through several connected profiles; one of them is active.
|
|
290
|
+
class AccountPlacement < Model
|
|
291
|
+
attr_accessor :id, :platform, :external_id, :name, :avatar_url, :status,
|
|
292
|
+
:active_profile, :linked_profiles
|
|
293
|
+
|
|
294
|
+
def initialize(**attrs)
|
|
295
|
+
@avatar_url = nil
|
|
296
|
+
@active_profile = nil
|
|
297
|
+
@linked_profiles = []
|
|
298
|
+
super
|
|
299
|
+
if @active_profile.is_a?(Hash)
|
|
300
|
+
@active_profile = AccountPlacementProfile.new(**@active_profile.transform_keys(&:to_sym))
|
|
301
|
+
end
|
|
302
|
+
@linked_profiles = (@linked_profiles || []).map do |p|
|
|
303
|
+
p.is_a?(AccountPlacementProfile) ? p : AccountPlacementProfile.new(**p.transform_keys(&:to_sym))
|
|
304
|
+
end
|
|
305
|
+
end
|
|
306
|
+
end
|
|
307
|
+
|
|
308
|
+
class ApiKey < Model
|
|
309
|
+
# `token` is only returned by ApiKeys#create — store it, it can't be read
|
|
310
|
+
# again.
|
|
311
|
+
attr_accessor :id, :name, :profile_group_id, :sandbox, :token, :created_at
|
|
312
|
+
|
|
313
|
+
def initialize(**attrs)
|
|
314
|
+
@profile_group_id = nil
|
|
315
|
+
@sandbox = false
|
|
316
|
+
@token = nil
|
|
317
|
+
super
|
|
318
|
+
@created_at = parse_time(@created_at)
|
|
319
|
+
end
|
|
320
|
+
|
|
321
|
+
private
|
|
322
|
+
|
|
323
|
+
def parse_time(value)
|
|
324
|
+
return nil if value.nil?
|
|
325
|
+
value.is_a?(Time) ? value : Time.parse(value.to_s)
|
|
326
|
+
end
|
|
241
327
|
end
|
|
242
328
|
|
|
243
329
|
# Instagram DM ice breaker (FAQ prompt). May carry extra platform fields.
|
|
@@ -365,7 +451,8 @@ module PostProxy
|
|
|
365
451
|
attr_accessor :id, :external_id, :body, :status, :author_username,
|
|
366
452
|
:author_avatar_url, :author_external_id, :metadata,
|
|
367
453
|
:parent_external_id, :like_count, :is_hidden, :permalink,
|
|
368
|
-
:platform_data, :attachments, :
|
|
454
|
+
:platform_data, :attachments, :external_edited_at, :posted_at,
|
|
455
|
+
:created_at, :replies
|
|
369
456
|
|
|
370
457
|
def initialize(**attrs)
|
|
371
458
|
@external_id = nil
|
|
@@ -378,8 +465,10 @@ module PostProxy
|
|
|
378
465
|
@permalink = nil
|
|
379
466
|
@platform_data = nil
|
|
380
467
|
@attachments = []
|
|
468
|
+
@external_edited_at = nil
|
|
381
469
|
@replies = []
|
|
382
470
|
super
|
|
471
|
+
@external_edited_at = parse_time(@external_edited_at)
|
|
383
472
|
@posted_at = parse_time(@posted_at)
|
|
384
473
|
@created_at = parse_time(@created_at)
|
|
385
474
|
@attachments = (@attachments || []).map do |a|
|
|
@@ -399,12 +488,21 @@ module PostProxy
|
|
|
399
488
|
end
|
|
400
489
|
|
|
401
490
|
class Chat < Model
|
|
402
|
-
|
|
403
|
-
|
|
491
|
+
# `external_placement_id` is the page / phone number the chat runs through;
|
|
492
|
+
# `group` marks a WhatsApp group chat. `within_messaging_window` is
|
|
493
|
+
# WhatsApp-only: whether the 24-hour customer service window is open —
|
|
494
|
+
# when it's closed only a template can be sent.
|
|
495
|
+
attr_accessor :id, :profile_id, :platform, :external_placement_id, :group,
|
|
496
|
+
:participant_external_id, :participant_username,
|
|
497
|
+
:participant_name, :participant_avatar_url,
|
|
404
498
|
:external_conversation_id, :last_inbound_at, :last_outbound_at,
|
|
405
|
-
:last_message_at, :metadata, :archived,
|
|
499
|
+
:last_message_at, :metadata, :archived,
|
|
500
|
+
:within_messaging_window, :created_at
|
|
406
501
|
|
|
407
502
|
def initialize(**attrs)
|
|
503
|
+
@external_placement_id = nil
|
|
504
|
+
@group = false
|
|
505
|
+
@within_messaging_window = nil
|
|
408
506
|
@participant_username = nil
|
|
409
507
|
@participant_name = nil
|
|
410
508
|
@participant_avatar_url = nil
|
|
@@ -429,13 +527,176 @@ module PostProxy
|
|
|
429
527
|
end
|
|
430
528
|
end
|
|
431
529
|
|
|
530
|
+
# A tappable chip above the participant's composer, gone once tapped.
|
|
531
|
+
# Facebook and Instagram only; up to 13 per send. +content_type+ is optional
|
|
532
|
+
# on send (only "text" is accepted) and always present on responses.
|
|
533
|
+
class QuickReply < Model
|
|
534
|
+
attr_accessor :content_type, :title, :payload
|
|
535
|
+
|
|
536
|
+
def initialize(**attrs)
|
|
537
|
+
@content_type = nil
|
|
538
|
+
super
|
|
539
|
+
end
|
|
540
|
+
|
|
541
|
+
def to_h
|
|
542
|
+
{ content_type: @content_type, title: @title, payload: @payload }.compact
|
|
543
|
+
end
|
|
544
|
+
end
|
|
545
|
+
|
|
546
|
+
# A button attached to the message, delivered as a Meta generic template.
|
|
547
|
+
# Facebook and Instagram only; up to 3 per send. +url+ is required and must be
|
|
548
|
+
# https when +type+ is "web_url"; +payload+ is required when +type+ is
|
|
549
|
+
# "postback". +type+ is a plain string rather than an enum so a new Meta
|
|
550
|
+
# button type needs no SDK release.
|
|
551
|
+
class MessageButton < Model
|
|
552
|
+
attr_accessor :type, :title, :url, :payload
|
|
553
|
+
|
|
554
|
+
def initialize(**attrs)
|
|
555
|
+
@url = nil
|
|
556
|
+
@payload = nil
|
|
557
|
+
super
|
|
558
|
+
end
|
|
559
|
+
|
|
560
|
+
def to_h
|
|
561
|
+
{ type: @type, title: @title, url: @url, payload: @payload }.compact
|
|
562
|
+
end
|
|
563
|
+
end
|
|
564
|
+
|
|
565
|
+
class CardDefaultAction < Model
|
|
566
|
+
attr_accessor :type, :url
|
|
567
|
+
|
|
568
|
+
def to_h
|
|
569
|
+
{ type: @type, url: @url }.compact
|
|
570
|
+
end
|
|
571
|
+
end
|
|
572
|
+
|
|
573
|
+
# Extra fields for the generic-template element that carries +buttons+.
|
|
574
|
+
# Requires +buttons+. +subtitle+ is capped at 80 characters, and both URLs
|
|
575
|
+
# must be https.
|
|
576
|
+
class MessageCard < Model
|
|
577
|
+
attr_accessor :subtitle, :image_url, :default_action
|
|
578
|
+
|
|
579
|
+
def initialize(**attrs)
|
|
580
|
+
@subtitle = nil
|
|
581
|
+
@image_url = nil
|
|
582
|
+
@default_action = nil
|
|
583
|
+
super
|
|
584
|
+
if @default_action && !@default_action.is_a?(CardDefaultAction)
|
|
585
|
+
@default_action = CardDefaultAction.new(**@default_action.transform_keys(&:to_sym))
|
|
586
|
+
end
|
|
587
|
+
end
|
|
588
|
+
|
|
589
|
+
def to_h
|
|
590
|
+
{
|
|
591
|
+
subtitle: @subtitle,
|
|
592
|
+
image_url: @image_url,
|
|
593
|
+
default_action: @default_action&.to_h
|
|
594
|
+
}.compact
|
|
595
|
+
end
|
|
596
|
+
end
|
|
597
|
+
|
|
598
|
+
# One card of a horizontally scrolling carousel. Facebook and Instagram only;
|
|
599
|
+
# up to 10 per send. +title+ (80 characters or fewer) plus at least one
|
|
600
|
+
# other field is required; +buttons+ takes up to 3.
|
|
601
|
+
class MessageElement < Model
|
|
602
|
+
attr_accessor :title, :subtitle, :image_url, :default_action, :buttons
|
|
603
|
+
|
|
604
|
+
def initialize(**attrs)
|
|
605
|
+
@subtitle = nil
|
|
606
|
+
@image_url = nil
|
|
607
|
+
@default_action = nil
|
|
608
|
+
@buttons = nil
|
|
609
|
+
super
|
|
610
|
+
if @default_action && !@default_action.is_a?(CardDefaultAction)
|
|
611
|
+
@default_action = CardDefaultAction.new(**@default_action.transform_keys(&:to_sym))
|
|
612
|
+
end
|
|
613
|
+
@buttons = @buttons&.map do |b|
|
|
614
|
+
b.is_a?(MessageButton) ? b : MessageButton.new(**b.transform_keys(&:to_sym))
|
|
615
|
+
end
|
|
616
|
+
end
|
|
617
|
+
|
|
618
|
+
def to_h
|
|
619
|
+
{
|
|
620
|
+
title: @title,
|
|
621
|
+
subtitle: @subtitle,
|
|
622
|
+
image_url: @image_url,
|
|
623
|
+
default_action: @default_action&.to_h,
|
|
624
|
+
buttons: @buttons&.map(&:to_h)
|
|
625
|
+
}.compact
|
|
626
|
+
end
|
|
627
|
+
end
|
|
628
|
+
|
|
629
|
+
class MessageLocation < Model
|
|
630
|
+
attr_accessor :latitude, :longitude, :name, :address
|
|
631
|
+
|
|
632
|
+
def initialize(**attrs)
|
|
633
|
+
@name = nil
|
|
634
|
+
@address = nil
|
|
635
|
+
super
|
|
636
|
+
end
|
|
637
|
+
|
|
638
|
+
def to_h
|
|
639
|
+
{ latitude: @latitude, longitude: @longitude, name: @name, address: @address }.compact
|
|
640
|
+
end
|
|
641
|
+
end
|
|
642
|
+
|
|
643
|
+
# WhatsApp template send. Address the template by +id+ (PostProxy or Meta
|
|
644
|
+
# id) or by +name+ (+ +language+ when it exists in several). +variables+ are
|
|
645
|
+
# positional: header, then body, then dynamic URL buttons. +button_params+
|
|
646
|
+
# are hashes with +index+ and +sub_type+ ("url", "copy_code", "flow",
|
|
647
|
+
# "quick_reply", "voice_call"); +header_media+ is { link: } or { id: } for
|
|
648
|
+
# an image / video / document header.
|
|
649
|
+
class MessageTemplateInput < Model
|
|
650
|
+
attr_accessor :id, :name, :language, :variables, :button_params,
|
|
651
|
+
:header_media, :header_location
|
|
652
|
+
|
|
653
|
+
def to_h
|
|
654
|
+
{
|
|
655
|
+
id: @id,
|
|
656
|
+
name: @name,
|
|
657
|
+
language: @language,
|
|
658
|
+
variables: @variables,
|
|
659
|
+
button_params: @button_params,
|
|
660
|
+
header_media: @header_media,
|
|
661
|
+
header_location: @header_location.is_a?(MessageLocation) ? @header_location.to_h : @header_location
|
|
662
|
+
}.compact
|
|
663
|
+
end
|
|
664
|
+
end
|
|
665
|
+
|
|
666
|
+
# The template an outbound WhatsApp message was sent with.
|
|
667
|
+
class MessageTemplateRef < Model
|
|
668
|
+
attr_accessor :id, :external_id, :name, :language, :variables
|
|
669
|
+
|
|
670
|
+
def initialize(**attrs)
|
|
671
|
+
@external_id = nil
|
|
672
|
+
@variables = nil
|
|
673
|
+
super
|
|
674
|
+
end
|
|
675
|
+
end
|
|
676
|
+
|
|
677
|
+
# Set on inbound messages created by a tap on an element you sent. Derived
|
|
678
|
+
# from platform_data rather than stored, so it also resolves for taps ingested
|
|
679
|
+
# before PostProxy exposed this field. +kind+ is one of "quick_reply",
|
|
680
|
+
# "postback", or "callback_query" — the last is Telegram, so this is not
|
|
681
|
+
# Meta-only even though the send params are.
|
|
682
|
+
class TappedAction < Model
|
|
683
|
+
attr_accessor :kind, :payload, :title
|
|
684
|
+
|
|
685
|
+
def initialize(**attrs)
|
|
686
|
+
@title = nil
|
|
687
|
+
super
|
|
688
|
+
end
|
|
689
|
+
end
|
|
690
|
+
|
|
432
691
|
class Message < Model
|
|
433
692
|
attr_accessor :id, :chat_id, :external_id, :direction, :body, :status,
|
|
434
|
-
:tag, :external_comment_id, :error_message, :
|
|
435
|
-
:
|
|
436
|
-
:
|
|
437
|
-
:
|
|
438
|
-
:
|
|
693
|
+
:tag, :external_comment_id, :error_message, :error_details,
|
|
694
|
+
:platform_data, :external_posted_at, :external_delivered_at,
|
|
695
|
+
:external_read_at, :external_edited_at, :reply_to_external_id,
|
|
696
|
+
:reply_markup, :quick_replies, :buttons, :card, :elements,
|
|
697
|
+
:tapped_action, :story_reply, :interactive, :location,
|
|
698
|
+
:contacts, :template, :external_deleted_at, :reactions,
|
|
699
|
+
:attachments, :is_unsupported, :created_at
|
|
439
700
|
|
|
440
701
|
def initialize(**attrs)
|
|
441
702
|
@external_id = nil
|
|
@@ -450,6 +711,18 @@ module PostProxy
|
|
|
450
711
|
@external_edited_at = nil
|
|
451
712
|
@reply_to_external_id = nil
|
|
452
713
|
@reply_markup = nil
|
|
714
|
+
@quick_replies = nil
|
|
715
|
+
@buttons = nil
|
|
716
|
+
@card = nil
|
|
717
|
+
@elements = nil
|
|
718
|
+
@tapped_action = nil
|
|
719
|
+
@error_details = nil
|
|
720
|
+
@story_reply = nil
|
|
721
|
+
# WhatsApp-only payloads.
|
|
722
|
+
@interactive = nil
|
|
723
|
+
@location = nil
|
|
724
|
+
@contacts = nil
|
|
725
|
+
@template = nil
|
|
453
726
|
@external_deleted_at = nil
|
|
454
727
|
@reactions = []
|
|
455
728
|
@attachments = []
|
|
@@ -467,6 +740,30 @@ module PostProxy
|
|
|
467
740
|
@attachments = (@attachments || []).map do |a|
|
|
468
741
|
a.is_a?(Attachment) ? a : Attachment.new(**a.transform_keys(&:to_sym))
|
|
469
742
|
end
|
|
743
|
+
# Left nil rather than [] when absent — the API omits these on non-Meta
|
|
744
|
+
# networks, and an empty array would read as "sent with none".
|
|
745
|
+
@quick_replies = @quick_replies&.map do |q|
|
|
746
|
+
q.is_a?(QuickReply) ? q : QuickReply.new(**q.transform_keys(&:to_sym))
|
|
747
|
+
end
|
|
748
|
+
@buttons = @buttons&.map do |b|
|
|
749
|
+
b.is_a?(MessageButton) ? b : MessageButton.new(**b.transform_keys(&:to_sym))
|
|
750
|
+
end
|
|
751
|
+
if @card && !@card.is_a?(MessageCard)
|
|
752
|
+
@card = MessageCard.new(**@card.transform_keys(&:to_sym))
|
|
753
|
+
end
|
|
754
|
+
if @tapped_action && !@tapped_action.is_a?(TappedAction)
|
|
755
|
+
@tapped_action = TappedAction.new(**@tapped_action.transform_keys(&:to_sym))
|
|
756
|
+
end
|
|
757
|
+
@elements = @elements&.map do |e|
|
|
758
|
+
e.is_a?(MessageElement) ? e : MessageElement.new(**e.transform_keys(&:to_sym))
|
|
759
|
+
end
|
|
760
|
+
if @location.is_a?(Hash)
|
|
761
|
+
@location = MessageLocation.new(**@location.transform_keys(&:to_sym))
|
|
762
|
+
end
|
|
763
|
+
if @template.is_a?(Hash)
|
|
764
|
+
@template = MessageTemplateRef.new(**@template.transform_keys(&:to_sym))
|
|
765
|
+
end
|
|
766
|
+
@error_details = ErrorDetails.new(**@error_details.transform_keys(&:to_sym)) if @error_details.is_a?(Hash)
|
|
470
767
|
end
|
|
471
768
|
|
|
472
769
|
private
|
|
@@ -516,7 +813,7 @@ module PostProxy
|
|
|
516
813
|
:status, :author_username, :author_avatar_url,
|
|
517
814
|
:author_external_id, :metadata, :parent_external_id,
|
|
518
815
|
:like_count, :is_hidden, :permalink, :platform_data,
|
|
519
|
-
:attachments, :posted_at, :created_at
|
|
816
|
+
:attachments, :external_edited_at, :posted_at, :created_at
|
|
520
817
|
|
|
521
818
|
def initialize(**attrs)
|
|
522
819
|
@external_id = nil
|
|
@@ -530,8 +827,10 @@ module PostProxy
|
|
|
530
827
|
@permalink = nil
|
|
531
828
|
@platform_data = nil
|
|
532
829
|
@attachments = []
|
|
830
|
+
@external_edited_at = nil
|
|
533
831
|
@posted_at = nil
|
|
534
832
|
super
|
|
833
|
+
@external_edited_at = parse_time(@external_edited_at)
|
|
535
834
|
@posted_at = parse_time(@posted_at)
|
|
536
835
|
@created_at = parse_time(@created_at)
|
|
537
836
|
@attachments = (@attachments || []).map do |a|
|
|
@@ -550,11 +849,12 @@ module PostProxy
|
|
|
550
849
|
# A record of one post pull for a profile — the sync fired when the profile
|
|
551
850
|
# connects, the recurring poll, or a backfill.
|
|
552
851
|
class PostSync < Model
|
|
553
|
-
attr_accessor :id, :profile_id, :kind, :trigger, :status,
|
|
554
|
-
:completed_at, :posts_seen, :posts_imported,
|
|
555
|
-
:oldest_posted_at, :error, :created_at
|
|
852
|
+
attr_accessor :id, :profile_id, :placement_id, :kind, :trigger, :status,
|
|
853
|
+
:started_at, :completed_at, :posts_seen, :posts_imported,
|
|
854
|
+
:backfill_from, :oldest_posted_at, :error, :created_at
|
|
556
855
|
|
|
557
856
|
def initialize(**attrs)
|
|
857
|
+
@placement_id = nil
|
|
558
858
|
@started_at = nil
|
|
559
859
|
@completed_at = nil
|
|
560
860
|
@posts_seen = 0
|
|
@@ -581,10 +881,46 @@ module PostProxy
|
|
|
581
881
|
end
|
|
582
882
|
end
|
|
583
883
|
|
|
884
|
+
# A chat (DM) sync run, started by Profiles#backfill_chats or on connect.
|
|
885
|
+
class ChatSync < Model
|
|
886
|
+
attr_accessor :id, :profile_id, :placement_id, :kind, :trigger, :status,
|
|
887
|
+
:started_at, :completed_at, :chats_seen, :messages_imported,
|
|
888
|
+
:backfill_from, :oldest_updated_at, :error, :created_at
|
|
889
|
+
|
|
890
|
+
def initialize(**attrs)
|
|
891
|
+
@placement_id = nil
|
|
892
|
+
@started_at = nil
|
|
893
|
+
@completed_at = nil
|
|
894
|
+
@chats_seen = 0
|
|
895
|
+
@messages_imported = 0
|
|
896
|
+
@backfill_from = nil
|
|
897
|
+
@oldest_updated_at = nil
|
|
898
|
+
@error = nil
|
|
899
|
+
super
|
|
900
|
+
@started_at = parse_time(@started_at)
|
|
901
|
+
@completed_at = parse_time(@completed_at)
|
|
902
|
+
@backfill_from = parse_time(@backfill_from)
|
|
903
|
+
@oldest_updated_at = parse_time(@oldest_updated_at)
|
|
904
|
+
@created_at = parse_time(@created_at)
|
|
905
|
+
end
|
|
906
|
+
|
|
907
|
+
private
|
|
908
|
+
|
|
909
|
+
def parse_time(value)
|
|
910
|
+
return nil if value.nil?
|
|
911
|
+
value.is_a?(Time) ? value : Time.parse(value.to_s)
|
|
912
|
+
end
|
|
913
|
+
end
|
|
914
|
+
|
|
584
915
|
class DeleteResponse < Model
|
|
585
916
|
attr_accessor :deleted
|
|
586
917
|
end
|
|
587
918
|
|
|
919
|
+
# Body bytes and content type of a binary download (WhatsApp#media).
|
|
920
|
+
class RawResponse < Model
|
|
921
|
+
attr_accessor :data, :content_type
|
|
922
|
+
end
|
|
923
|
+
|
|
588
924
|
class DeletingPlatform < Model
|
|
589
925
|
attr_accessor :post_profile_id, :platform
|
|
590
926
|
end
|
|
@@ -659,10 +995,101 @@ module PostProxy
|
|
|
659
995
|
end
|
|
660
996
|
end
|
|
661
997
|
|
|
998
|
+
# One day of a DailyStats series. Normalized metrics (e.g. `impressions`,
|
|
999
|
+
# `viewers`) vary by platform, so they live in `values` and are also
|
|
1000
|
+
# readable as `record[:impressions]`.
|
|
1001
|
+
class DailyStatsRecord
|
|
1002
|
+
attr_reader :date, :platform_stats, :values
|
|
1003
|
+
|
|
1004
|
+
def initialize(**attrs)
|
|
1005
|
+
attrs = attrs.transform_keys(&:to_sym)
|
|
1006
|
+
@date = attrs.delete(:date)
|
|
1007
|
+
@platform_stats = attrs.delete(:platform_stats)
|
|
1008
|
+
@values = attrs
|
|
1009
|
+
end
|
|
1010
|
+
|
|
1011
|
+
def [](key)
|
|
1012
|
+
@values[key.to_sym]
|
|
1013
|
+
end
|
|
1014
|
+
|
|
1015
|
+
def to_h
|
|
1016
|
+
{ date: @date, platform_stats: @platform_stats }.merge(@values)
|
|
1017
|
+
end
|
|
1018
|
+
end
|
|
1019
|
+
|
|
1020
|
+
class DailyStats < Model
|
|
1021
|
+
# `metrics` describes each metric in `records`.
|
|
1022
|
+
attr_accessor :profile_id, :platform, :placement_id, :attribution, :from,
|
|
1023
|
+
:to, :data_timezone, :records, :metrics
|
|
1024
|
+
|
|
1025
|
+
def initialize(**attrs)
|
|
1026
|
+
@placement_id = nil
|
|
1027
|
+
@data_timezone = nil
|
|
1028
|
+
@records = []
|
|
1029
|
+
@metrics = {}
|
|
1030
|
+
super
|
|
1031
|
+
@records = (@records || []).map do |r|
|
|
1032
|
+
r.is_a?(DailyStatsRecord) ? r : DailyStatsRecord.new(**r)
|
|
1033
|
+
end
|
|
1034
|
+
end
|
|
1035
|
+
end
|
|
1036
|
+
|
|
1037
|
+
class DailyStatsResponse
|
|
1038
|
+
attr_reader :data
|
|
1039
|
+
|
|
1040
|
+
def initialize(data:)
|
|
1041
|
+
@data = data.is_a?(DailyStats) ? data : DailyStats.new(**data.transform_keys(&:to_sym))
|
|
1042
|
+
end
|
|
1043
|
+
end
|
|
1044
|
+
|
|
1045
|
+
class FollowerHistoryRecord < Model
|
|
1046
|
+
attr_accessor :date, :followers_gained, :followers_lost, :followers_change, :followers_total
|
|
1047
|
+
end
|
|
1048
|
+
|
|
1049
|
+
class FollowerHistory < Model
|
|
1050
|
+
attr_accessor :profile_id, :platform, :placement_id, :from, :to, :records
|
|
1051
|
+
|
|
1052
|
+
def initialize(**attrs)
|
|
1053
|
+
@placement_id = nil
|
|
1054
|
+
@records = []
|
|
1055
|
+
super
|
|
1056
|
+
@records = (@records || []).map do |r|
|
|
1057
|
+
r.is_a?(FollowerHistoryRecord) ? r : FollowerHistoryRecord.new(**r.transform_keys(&:to_sym))
|
|
1058
|
+
end
|
|
1059
|
+
end
|
|
1060
|
+
end
|
|
1061
|
+
|
|
1062
|
+
class FollowerHistoryResponse
|
|
1063
|
+
attr_reader :data
|
|
1064
|
+
|
|
1065
|
+
def initialize(data:)
|
|
1066
|
+
@data = data.is_a?(FollowerHistory) ? data : FollowerHistory.new(**data.transform_keys(&:to_sym))
|
|
1067
|
+
end
|
|
1068
|
+
end
|
|
1069
|
+
|
|
1070
|
+
# Instagram only. Cached for a short while server-side.
|
|
1071
|
+
class ParticipantStatus < Model
|
|
1072
|
+
attr_accessor :is_verified_user, :is_user_follow_business,
|
|
1073
|
+
:is_business_follow_user, :fetched_at
|
|
1074
|
+
end
|
|
1075
|
+
|
|
1076
|
+
# LinkedIn only: a company page URL (or vanity name) resolved to the URN you
|
|
1077
|
+
# @-mention it with in a post body.
|
|
1078
|
+
class ResolvedMention < Model
|
|
1079
|
+
attr_accessor :urn, :type, :name, :vanity_name
|
|
1080
|
+
end
|
|
1081
|
+
|
|
662
1082
|
# Platform-specific parameter structs
|
|
663
1083
|
|
|
1084
|
+
# `alt_text` on every params class that has it takes a single string or an
|
|
1085
|
+
# array, one per media item in order.
|
|
1086
|
+
#
|
|
1087
|
+
# `link` makes a text-only link post (Facebook renders the preview card).
|
|
1088
|
+
# `made_with_ai` applies to photo posts only. `countries` are ISO 3166-1
|
|
1089
|
+
# alpha-2 codes; viewers elsewhere won't see the post.
|
|
664
1090
|
class FacebookParams < Model
|
|
665
|
-
attr_accessor :format, :title, :first_comment, :page_id
|
|
1091
|
+
attr_accessor :format, :title, :first_comment, :page_id, :link, :alt_text,
|
|
1092
|
+
:made_with_ai, :countries
|
|
666
1093
|
end
|
|
667
1094
|
|
|
668
1095
|
# An Instagram account to tag in a post. Images require `x` and `y`; reels
|
|
@@ -672,9 +1099,20 @@ module PostProxy
|
|
|
672
1099
|
attr_accessor :username, :x, :y, :media_index
|
|
673
1100
|
end
|
|
674
1101
|
|
|
1102
|
+
# `cover_file` is a local path to a cover image, uploaded with the post (the
|
|
1103
|
+
# request switches to multipart); a base64 data URL
|
|
1104
|
+
# (`data:image/jpeg;base64,...`) is sent as-is. The same holds for TikTok,
|
|
1105
|
+
# YouTube and Pinterest.
|
|
1106
|
+
#
|
|
1107
|
+
# `audio_id` comes from Profiles#instagram_available_audio; it and
|
|
1108
|
+
# `audio_volume` / `video_volume` (1-100) need a profile connected via
|
|
1109
|
+
# Facebook login. `strip_audio` removes the video's own track (reel, story).
|
|
1110
|
+
# `trial_strategy` is "MANUAL" or "SS_PERFORMANCE".
|
|
675
1111
|
class InstagramParams < Model
|
|
676
1112
|
attr_accessor :format, :first_comment, :collaborators, :cover_url,
|
|
677
|
-
:
|
|
1113
|
+
:cover_file, :audio_name, :audio_id, :audio_volume,
|
|
1114
|
+
:video_volume, :strip_audio, :trial_strategy, :thumb_offset,
|
|
1115
|
+
:user_tags, :alt_text, :made_with_ai
|
|
678
1116
|
|
|
679
1117
|
def initialize(**attrs)
|
|
680
1118
|
@user_tags = nil
|
|
@@ -698,37 +1136,50 @@ module PostProxy
|
|
|
698
1136
|
end
|
|
699
1137
|
end
|
|
700
1138
|
|
|
1139
|
+
# `upload_as_draft` sends the video to the creator's TikTok inbox instead of
|
|
1140
|
+
# publishing. `cover_url` needs profiles connected after 2026-09-30;
|
|
1141
|
+
# `thumb_offset` picks a frame in milliseconds, and `cover_url` wins if both
|
|
1142
|
+
# are set.
|
|
701
1143
|
class TikTokParams < Model
|
|
702
1144
|
attr_accessor :format, :privacy_status, :photo_cover_index, :auto_add_music,
|
|
703
1145
|
:made_with_ai, :disable_comment, :disable_duet, :disable_stitch,
|
|
704
|
-
:brand_content_toggle, :brand_organic_toggle
|
|
1146
|
+
:brand_content_toggle, :brand_organic_toggle, :upload_as_draft,
|
|
1147
|
+
:cover_url, :cover_file, :thumb_offset
|
|
705
1148
|
end
|
|
706
1149
|
|
|
1150
|
+
# `reshare_urn` reshares a post (e.g. "urn:li:share:123"). `countries` is
|
|
1151
|
+
# for organization pages only (up to 25 codes). For format "poll",
|
|
1152
|
+
# `poll_question` (140 chars max) and `poll_options` (2-4, 30 chars max
|
|
1153
|
+
# each) are required; `poll_duration_days` is 1, 3, 7 (default) or 14.
|
|
707
1154
|
class LinkedInParams < Model
|
|
708
|
-
attr_accessor :format, :organization_id
|
|
1155
|
+
attr_accessor :format, :organization_id, :reshare_urn, :alt_text, :countries,
|
|
1156
|
+
:poll_question, :poll_options, :poll_duration_days
|
|
709
1157
|
end
|
|
710
1158
|
|
|
711
1159
|
class YouTubeParams < Model
|
|
712
|
-
attr_accessor :format, :title, :privacy_status, :cover_url, :
|
|
713
|
-
:tags, :category_id, :contains_synthetic_media
|
|
1160
|
+
attr_accessor :format, :title, :privacy_status, :cover_url, :cover_file,
|
|
1161
|
+
:made_for_kids, :tags, :category_id, :contains_synthetic_media
|
|
714
1162
|
end
|
|
715
1163
|
|
|
716
1164
|
class PinterestParams < Model
|
|
717
|
-
attr_accessor :format, :title, :board_id, :destination_link, :cover_url,
|
|
1165
|
+
attr_accessor :format, :title, :board_id, :destination_link, :cover_url,
|
|
1166
|
+
:cover_file, :thumb_offset, :alt_text
|
|
718
1167
|
end
|
|
719
1168
|
|
|
1169
|
+
# `topic_tag` is one tag, 50 characters or fewer, without the leading # and
|
|
1170
|
+
# with no "." or "&".
|
|
720
1171
|
class ThreadsParams < Model
|
|
721
|
-
attr_accessor :format
|
|
1172
|
+
attr_accessor :format, :alt_text, :topic_tag
|
|
722
1173
|
end
|
|
723
1174
|
|
|
724
1175
|
class TwitterParams < Model
|
|
725
1176
|
# poll_options and poll_duration_minutes are required when format is
|
|
726
1177
|
# "poll": 2-4 options (max 25 chars each), duration 5 to 10080 minutes.
|
|
727
|
-
attr_accessor :format, :poll_options, :poll_duration_minutes
|
|
1178
|
+
attr_accessor :format, :alt_text, :poll_options, :poll_duration_minutes
|
|
728
1179
|
end
|
|
729
1180
|
|
|
730
1181
|
class BlueskyParams < Model
|
|
731
|
-
attr_accessor :format
|
|
1182
|
+
attr_accessor :format, :alt_text
|
|
732
1183
|
end
|
|
733
1184
|
|
|
734
1185
|
class TelegramParams < Model
|
|
@@ -751,4 +1202,33 @@ module PostProxy
|
|
|
751
1202
|
result
|
|
752
1203
|
end
|
|
753
1204
|
end
|
|
1205
|
+
|
|
1206
|
+
# --- WhatsApp ---
|
|
1207
|
+
|
|
1208
|
+
class WhatsAppTemplate < Model
|
|
1209
|
+
attr_accessor :id, :external_id, :profile_id, :name, :language, :category,
|
|
1210
|
+
:status, :parameter_format, :components,
|
|
1211
|
+
:message_send_ttl_seconds, :rejected_reason, :quality_score,
|
|
1212
|
+
:variable_count, :created_at, :updated_at
|
|
1213
|
+
|
|
1214
|
+
def initialize(**attrs)
|
|
1215
|
+
@external_id = nil
|
|
1216
|
+
@parameter_format = nil
|
|
1217
|
+
@components = []
|
|
1218
|
+
@message_send_ttl_seconds = nil
|
|
1219
|
+
@rejected_reason = nil
|
|
1220
|
+
@quality_score = nil
|
|
1221
|
+
@variable_count = 0
|
|
1222
|
+
super
|
|
1223
|
+
@created_at = parse_time(@created_at)
|
|
1224
|
+
@updated_at = parse_time(@updated_at)
|
|
1225
|
+
end
|
|
1226
|
+
|
|
1227
|
+
private
|
|
1228
|
+
|
|
1229
|
+
def parse_time(value)
|
|
1230
|
+
return nil if value.nil?
|
|
1231
|
+
value.is_a?(Time) ? value : Time.parse(value.to_s)
|
|
1232
|
+
end
|
|
1233
|
+
end
|
|
754
1234
|
end
|