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.
@@ -19,11 +19,22 @@ module PostProxy
19
19
  end
20
20
 
21
21
  class Profile < Model
22
- attr_accessor :id, :name, :status, :platform, :profile_group_id, :expires_at, :post_count
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, :status, :params, :error, :error_details, :attempted_at, :insights
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
- attr_accessor :id, :body, :media
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, :posted_at, :created_at, :replies
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
- attr_accessor :id, :profile_id, :platform, :participant_external_id,
403
- :participant_username, :participant_name, :participant_avatar_url,
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, :created_at
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, :platform_data,
435
- :external_posted_at, :external_delivered_at, :external_read_at,
436
- :external_edited_at, :reply_to_external_id, :reply_markup,
437
- :external_deleted_at, :reactions, :attachments,
438
- :is_unsupported, :created_at
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, :started_at,
554
- :completed_at, :posts_seen, :posts_imported, :backfill_from,
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
- :audio_name, :trial_strategy, :thumb_offset, :user_tags
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, :made_for_kids,
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, :thumb_offset
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