blueticks 1.1.2 → 2.0.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.
Files changed (36) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +41 -0
  3. data/lib/blueticks/client.rb +27 -5
  4. data/lib/blueticks/resources/account.rb +2 -2
  5. data/lib/blueticks/resources/audiences.rb +30 -22
  6. data/lib/blueticks/resources/campaigns.rb +28 -23
  7. data/lib/blueticks/resources/chats.rb +37 -86
  8. data/lib/blueticks/resources/contacts.rb +18 -11
  9. data/lib/blueticks/resources/engines.rb +12 -19
  10. data/lib/blueticks/resources/groups.rb +54 -28
  11. data/lib/blueticks/resources/messages.rb +110 -31
  12. data/lib/blueticks/resources/newsletters.rb +43 -0
  13. data/lib/blueticks/resources/ping.rb +6 -3
  14. data/lib/blueticks/resources/scheduled_messages.rb +74 -32
  15. data/lib/blueticks/resources/suno.rb +56 -0
  16. data/lib/blueticks/resources/utils.rb +3 -13
  17. data/lib/blueticks/resources/webhooks.rb +20 -20
  18. data/lib/blueticks/types/account.rb +2 -1
  19. data/lib/blueticks/types/audiences.rb +15 -9
  20. data/lib/blueticks/types/campaigns.rb +13 -12
  21. data/lib/blueticks/types/chats.rb +19 -78
  22. data/lib/blueticks/types/contacts.rb +6 -7
  23. data/lib/blueticks/types/engines.rb +9 -8
  24. data/lib/blueticks/types/groups.rb +22 -6
  25. data/lib/blueticks/types/messages.rb +124 -11
  26. data/lib/blueticks/types/newsletter.rb +33 -0
  27. data/lib/blueticks/types/ok_result.rb +13 -0
  28. data/lib/blueticks/types/paginated.rb +70 -0
  29. data/lib/blueticks/types/ping.rb +11 -3
  30. data/lib/blueticks/types/scheduled_messages.rb +28 -14
  31. data/lib/blueticks/types/suno.rb +42 -0
  32. data/lib/blueticks/types/utils.rb +5 -12
  33. data/lib/blueticks/types/webhooks.rb +13 -11
  34. data/lib/blueticks/version.rb +1 -1
  35. data/lib/blueticks.rb +2 -2
  36. metadata +7 -1
@@ -4,26 +4,32 @@ require_relative "base"
4
4
 
5
5
  module Blueticks
6
6
  module Types
7
- class Contact < Base
7
+ # A contact stored inside an audience (ContactResponse). `variables` is a
8
+ # free-form hash of merge-field values.
9
+ class AudienceContact < Base
8
10
  field :id, :string
9
11
  field :to, :string
10
- field :variables, %i[hash_of string]
11
- field :added_at, :string
12
+ field :variables, :hash
13
+ field :added_at, :string, wire_name: "addedAt"
12
14
  end
13
15
 
16
+ # An audience (AudienceResponse).
14
17
  class Audience < Base
15
18
  field :id, :string
16
19
  field :name, :string
17
- field :contact_count, :integer
18
- field :created_at, :string
19
- field :contacts, [:array, Contact], optional: true
20
- field :page, :integer, optional: true
21
- field :has_more, :boolean, optional: true
20
+ field :contact_count, :integer, wire_name: "contactCount"
21
+ field :created_at, :string, wire_name: "createdAt"
22
22
  end
23
23
 
24
+ # Result of appending contacts to an audience (POST .../contacts).
24
25
  class AppendContactsResult < Base
25
26
  field :added, :integer
26
- field :contact_count, :integer
27
+ field :contact_count, :integer, wire_name: "contactCount"
28
+ end
29
+
30
+ # Result of removing a contact from an audience (DELETE .../contacts/{id}).
31
+ class ContactRemoval < Base
32
+ field :id, :string
27
33
  end
28
34
  end
29
35
  end
@@ -8,21 +8,22 @@ module Blueticks
8
8
  pending running paused complete_sent complete_delivered aborted
9
9
  ].freeze
10
10
 
11
+ # A campaign (CampaignResponse). Note the id is carried on `cmp_id`
12
+ # (wire `cmpId`), matching the backend document shape.
11
13
  class Campaign < Base
12
- field :id, :string
14
+ field :cmp_id, :string, wire_name: "cmpId"
13
15
  field :name, :string
14
- field :audience_id, :string
16
+ field :audience_id, :string, wire_name: "audienceId"
15
17
  field :status, [:enum, CAMPAIGN_STATUSES]
16
- field :total_count, :integer
17
- field :sent_count, :integer
18
- field :delivered_count, :integer
19
- field :read_count, :integer
20
- field :failed_count, :integer
21
- field :from, :string, nullable: true
22
- field :created_at, :string
23
- field :started_at, :string, nullable: true
24
- field :completed_at, :string, nullable: true
25
- field :aborted_at, :string, nullable: true
18
+ field :total_count, :integer, wire_name: "totalCount"
19
+ field :sent_count, :integer, wire_name: "sentCount"
20
+ field :delivered_count, :integer, wire_name: "deliveredCount"
21
+ field :read_count, :integer, wire_name: "readCount"
22
+ field :failed_count, :integer, wire_name: "failedCount"
23
+ field :created_at, :string, wire_name: "createdAt"
24
+ field :started_at, :string, nullable: true, wire_name: "startedAt"
25
+ field :completed_at, :string, nullable: true, wire_name: "completedAt"
26
+ field :aborted_at, :string, nullable: true, wire_name: "abortedAt"
26
27
  end
27
28
  end
28
29
  end
@@ -4,88 +4,29 @@ require_relative "base"
4
4
 
5
5
  module Blueticks
6
6
  module Types
7
- MESSAGE_TYPES = %w[
8
- chat image video document audio ptt sticker gif ptv
9
- poll_creation location vcard revoked
10
- ].freeze
11
-
12
- MEDIA_UNAVAILABLE_REASONS = %w[expired fetching awaiting_sender error no_media].freeze
7
+ CHAT_TYPES = %w[contact group newsletter].freeze
13
8
 
9
+ # A chat row (Chat) returned by chats.list / chats.retrieve.
14
10
  class Chat < Base
15
- field :id, :string
11
+ field :chat_id, :string, wire_name: "chatId"
16
12
  field :name, :string, nullable: true
17
- field :is_group, :boolean
18
- field :last_message_at, :string, nullable: true
19
- field :unread_count, :integer, nullable: true
20
- end
21
-
13
+ field :chat_type, [:enum, CHAT_TYPES], wire_name: "chatType"
14
+ field :pinned, :boolean, optional: true
15
+ field :archived, :boolean, optional: true
16
+ field :last_message_at, :string, nullable: true, wire_name: "lastMessageAt"
17
+ field :unread_count, :integer, nullable: true, wire_name: "unreadCount"
18
+ field :marked_unread, :boolean, wire_name: "markedUnread"
19
+ field :last_message_text, :string, nullable: true, optional: true, wire_name: "lastMessageText"
20
+ field :last_message_from_me, :boolean, nullable: true, optional: true, wire_name: "lastMessageFromMe"
21
+ end
22
+
23
+ # A participant of a group/community chat (Participant) as returned by
24
+ # chats.list_participants.
22
25
  class Participant < Base
23
- field :chat_id, :string
24
- field :is_admin, :boolean
25
- field :is_super_admin, :boolean, nullable: true
26
- end
27
-
28
- class ChatMessage < Base
29
- field :key, :string
30
- field :chat_id, :string
31
- field :from, :string
32
- field :timestamp, :string, nullable: true
33
- field :text, :string, nullable: true
34
- field :type, :string
35
- field :from_me, :boolean
36
- field :ack, :integer, nullable: true
37
- field :media_url, :string, nullable: true
38
- field :caption, :string, nullable: true
39
- field :filename, :string, nullable: true
40
- end
41
-
42
- class ChatMedia < Base
43
- field :url, :string, nullable: true
44
- field :mimetype, :string, nullable: true
45
- field :filename, :string, nullable: true
46
- field :data_base64, :string, nullable: true
47
- field :original_quality, :boolean, nullable: true
48
- field :media_unavailable, [:enum, MEDIA_UNAVAILABLE_REASONS], nullable: true
49
- end
50
-
51
- # Generic `{ "ok": true }` response envelope returned by acknowledged side-effects.
52
- class OkResponse < Base
53
- field :ok, :boolean
54
- end
55
-
56
- # `{ "chat_id": "..." }` envelope returned by /v1/chats/{chat_id}/open.
57
- class ChatRef < Base
58
- field :chat_id, :string
59
- end
60
-
61
- # `{ "ack": int|null }` envelope returned by .../messages/{key}/ack.
62
- class MessageAck < Base
63
- field :ack, :integer, nullable: true
64
- end
65
-
66
- # Response from POST /v1/chats/{chat_id}/messages/load_older.
67
- class LoadOlderMessagesResponse < Base
68
- field :total_messages, :integer, nullable: true
69
- field :added, :integer, nullable: true
70
- field :can_load_more, :boolean
71
- end
72
-
73
- # Response from GET /v1/chats/{chat_id}/messages/{key}/media_url.
74
- class MediaUrlResponse < Base
75
- field :url, :string, nullable: true
76
- end
77
-
78
- # Single entry in the BatchMessageAcksResponse `data` array.
79
- # `{ "key": "<message key>", "ack": <int|null> }`.
80
- class BatchMessageAckEntry < Base
81
- field :key, :string
82
- field :ack, :integer, nullable: true
83
- end
84
-
85
- # Response from POST /v1/chats/message_acks.
86
- # `{ "data": [BatchMessageAckEntry, ...] }`.
87
- class BatchMessageAcksResponse < Base
88
- field :data, [:array, BatchMessageAckEntry]
26
+ field :chat_id, :string, wire_name: "chatId"
27
+ field :name, :string, nullable: true, optional: true
28
+ field :is_admin, :boolean, wire_name: "isAdmin"
29
+ field :is_super_admin, :boolean, optional: true, wire_name: "isSuperAdmin"
89
30
  end
90
31
  end
91
32
  end
@@ -4,14 +4,13 @@ require_relative "base"
4
4
 
5
5
  module Blueticks
6
6
  module Types
7
+ # A WhatsApp contact known to the connected engine (contacts.list item).
8
+ # Per the spec, only `chatId` is required; `name` and `isBusiness` are
9
+ # optional and absent from real engine responses.
7
10
  class WhatsAppContact < Base
8
- field :chat_id, :string
9
- field :name, :string, nullable: true
10
- field :is_business, :boolean
11
- end
12
-
13
- class ProfilePicture < Base
14
- field :url, :string, nullable: true
11
+ field :chat_id, :string, wire_name: "chatId"
12
+ field :name, :string, optional: true, nullable: true
13
+ field :is_business, :boolean, optional: true, nullable: true, wire_name: "isBusiness"
15
14
  end
16
15
  end
17
16
  end
@@ -4,17 +4,18 @@ require_relative "base"
4
4
 
5
5
  module Blueticks
6
6
  module Types
7
- class EngineStatus < Base
7
+ ENGINE_TYPES = %w[gateway regular].freeze
8
+
9
+ # A single engine session connected to the workspace (engines.list item).
10
+ # `type` is "gateway" (a remote server-side engine / Blueticks 24/7 gateway
11
+ # or baileys pod) or "regular" (the user's own WhatsApp Web extension).
12
+ class Engine < Base
13
+ field :id, :string
14
+ field :type, [:enum, ENGINE_TYPES]
8
15
  field :connected, :boolean
9
16
  field :state, :string, nullable: true
10
17
  field :stream, :string, nullable: true
11
- field :has_synced, :boolean, nullable: true
12
- end
13
-
14
- class WhatsAppMe < Base
15
- field :phone, :string, nullable: true
16
- field :name, :string, nullable: true
17
- field :platform, :string, nullable: true
18
+ field :has_synced, :boolean, nullable: true, wire_name: "hasSynced"
18
19
  end
19
20
  end
20
21
  end
@@ -4,24 +4,40 @@ require_relative "base"
4
4
 
5
5
  module Blueticks
6
6
  module Types
7
+ # A member of a group (GroupParticipant).
7
8
  class GroupParticipant < Base
8
- field :chat_id, :string
9
- field :is_admin, :boolean
10
- field :is_super_admin, :boolean
9
+ field :chat_id, :string, wire_name: "chatId"
10
+ field :is_admin, :boolean, wire_name: "isAdmin"
11
+ field :is_super_admin, :boolean, wire_name: "isSuperAdmin"
11
12
  field :name, :string, nullable: true
12
13
  end
13
14
 
15
+ # Full group metadata (Group) returned by group create/get/update and the
16
+ # membership-mutating endpoints.
14
17
  class Group < Base
15
18
  field :id, :string
16
19
  field :name, :string, nullable: true
17
20
  field :description, :string, nullable: true
18
21
  field :owner, :string, nullable: true
19
- field :created_at, :string, nullable: true
20
- field :last_message_at, :string, nullable: true
21
- field :participant_count, :integer, nullable: true
22
+ field :created_at, :string, nullable: true, wire_name: "createdAt"
23
+ field :last_message_at, :string, nullable: true, wire_name: "lastMessageAt"
24
+ field :participant_count, :integer, nullable: true, wire_name: "participantCount"
22
25
  field :announce, :boolean, nullable: true
23
26
  field :restrict, :boolean, nullable: true
24
27
  field :participants, [:array, GroupParticipant], nullable: true
25
28
  end
29
+
30
+ # A group row in the groups.list / contacts.common_groups page
31
+ # (GroupListItem) — a slimmer projection without announce/participants.
32
+ class GroupListItem < Base
33
+ field :id, :string
34
+ field :name, :string, nullable: true
35
+ field :description, :string, nullable: true
36
+ field :owner, :string, nullable: true
37
+ field :created_at, :string, nullable: true, wire_name: "createdAt"
38
+ field :last_message_at, :string, nullable: true, wire_name: "lastMessageAt"
39
+ field :participant_count, :integer, nullable: true, wire_name: "participantCount"
40
+ field :restrict, :boolean, nullable: true
41
+ end
26
42
  end
27
43
  end
@@ -4,22 +4,135 @@ require_relative "base"
4
4
 
5
5
  module Blueticks
6
6
  module Types
7
- MESSAGE_STATUSES = %w[scheduled queued sending delivered read failed].freeze
7
+ MESSAGE_KINDS = %w[text media poll].freeze
8
+ MESSAGE_MEDIA_KINDS = %w[image video audio document sticker voice gif].freeze
9
+ MEDIA_UNAVAILABLE_REASONS = %w[expired fetching awaiting_sender error no_media].freeze
10
+ MESSAGE_STATUSES = %w[
11
+ pending before-wa-send bt-sent sending sent sent_pending_ack confirmed
12
+ delivered received read played cancelled error failed expired
13
+ ].freeze
8
14
 
15
+ # WhatsApp wire message key (PublicWaMessageKey). `serialized` maps the
16
+ # wire's `_serialized` compact form.
17
+ class WaMessageKey < Base
18
+ field :from_me, :boolean, wire_name: "fromMe"
19
+ field :remote, :string, optional: true
20
+ field :id, :string, optional: true
21
+ field :serialized, :string, optional: true, wire_name: "_serialized"
22
+ field :participant, :string, optional: true
23
+ end
24
+
25
+ # Link preview attached to a queued/scheduled message (MessageResponse).
26
+ # Fields are always present but may be null.
27
+ class LinkPreview < Base
28
+ field :title, :string, nullable: true
29
+ field :description, :string, nullable: true
30
+ field :canonical_url, :string, nullable: true, wire_name: "canonicalUrl"
31
+ field :thumbnail, :string, nullable: true
32
+ end
33
+
34
+ # Link preview attached to a store message (PublicMessage). Fields are all
35
+ # optional here.
36
+ class PublicLinkPreview < Base
37
+ field :title, :string, optional: true
38
+ field :description, :string, optional: true
39
+ field :canonical_url, :string, optional: true, wire_name: "canonicalUrl"
40
+ field :thumbnail, :string, optional: true
41
+ end
42
+
43
+ # The message a store message replies to (PublicQuotedMessage).
44
+ class QuotedMessage < Base
45
+ field :wa_message_key, WaMessageKey, wire_name: "waMessageKey"
46
+ field :text, :string, optional: true
47
+ field :type, :string, optional: true
48
+ field :author, :string, optional: true
49
+ end
50
+
51
+ # A message read from the WhatsApp store (PublicMessage) — the shape
52
+ # returned by messages.list and messages.retrieve.
53
+ class PublicMessage < Base
54
+ field :wa_message_key, WaMessageKey, wire_name: "waMessageKey"
55
+ field :chat_id, :string, wire_name: "chatId"
56
+ field :from, :string
57
+ field :author, :string, optional: true
58
+ field :sender_name, :string, optional: true, wire_name: "senderName"
59
+ field :timestamp, :string, optional: true
60
+ field :text, :string, optional: true
61
+ field :type, :string
62
+ field :from_me, :boolean, wire_name: "fromMe"
63
+ field :ack, :integer, optional: true
64
+ field :media_url, :string, optional: true, wire_name: "mediaUrl"
65
+ field :filename, :string, optional: true
66
+ field :link_preview, PublicLinkPreview, optional: true, wire_name: "linkPreview"
67
+ field :quoted_message, QuotedMessage, optional: true, wire_name: "quotedMessage"
68
+ end
69
+
70
+ # Media payload for a message (Media) returned by messages.get_media.
71
+ class Media < Base
72
+ field :url, :string, optional: true
73
+ field :mimetype, :string, optional: true
74
+ field :filename, :string, optional: true
75
+ field :data_base64, :string, optional: true, wire_name: "dataBase64"
76
+ field :original_quality, :boolean, optional: true, wire_name: "originalQuality"
77
+ field :media_unavailable, [:enum, MEDIA_UNAVAILABLE_REASONS], optional: true, wire_name: "mediaUnavailable"
78
+ end
79
+
80
+ # Delivery acknowledgement (MessageAck). ack: -1=error, 0=pending,
81
+ # 1=server, 2=device, 3=read, 4=played.
82
+ class MessageAck < Base
83
+ field :ack, :integer, nullable: true
84
+ end
85
+
86
+ # A single entry in the messages.batch_acks page (BatchMessageAckEntry).
87
+ class BatchAckEntry < Base
88
+ field :key, :string
89
+ field :ack, :integer, nullable: true
90
+ field :found, :boolean
91
+ end
92
+
93
+ # A pinned message summary (PinnedMessage) in the messages.pinned page.
94
+ class PinnedMessage < Base
95
+ field :key, :string
96
+ field :chat_id, :string, wire_name: "chatId"
97
+ field :text, :string, nullable: true
98
+ end
99
+
100
+ # Result of a load-older request (LoadOlderResult).
101
+ class LoadOlderResult < Base
102
+ field :total_messages, :integer, nullable: true, wire_name: "totalMessages"
103
+ field :added, :integer, nullable: true
104
+ field :can_load_more, :boolean, wire_name: "canLoadMore"
105
+ field :history_unavailable, :boolean, wire_name: "historyUnavailable"
106
+ field :error, :string, nullable: true
107
+ end
108
+
109
+ # A sent/queued outbound message record — the shape returned by
110
+ # messages.create (direct send). The spec's send response marks only
111
+ # `to`, `type`, `status`, `createdAt` required; `waMessageKey` is optional
112
+ # (absent until the message has a wire key). The scheduled/queued variant is
113
+ # modelled separately by Types::ScheduledMessage.
9
114
  class Message < Base
10
- field :id, :string
115
+ field :id, :string, optional: true
116
+ field :wa_message_key, WaMessageKey, optional: true, nullable: true, wire_name: "waMessageKey"
11
117
  field :to, :string
12
- field :from, :string, nullable: true
118
+ field :type, [:enum, MESSAGE_KINDS]
13
119
  field :text, :string, nullable: true
14
- field :media_url, :string, nullable: true
120
+ field :media_url, :string, nullable: true, wire_name: "mediaUrl"
121
+ field :media_kind, [:enum, MESSAGE_MEDIA_KINDS], nullable: true, wire_name: "mediaKind"
122
+ field :poll_question, :string, nullable: true, wire_name: "pollQuestion"
123
+ field :poll_options, %i[array string], nullable: true, wire_name: "pollOptions"
124
+ field :poll_allow_multiple, :boolean, nullable: true, wire_name: "pollAllowMultiple"
15
125
  field :status, [:enum, MESSAGE_STATUSES]
16
- field :send_at, :string, nullable: true
17
- field :created_at, :string
18
- field :sent_at, :string, nullable: true
19
- field :delivered_at, :string, nullable: true
20
- field :read_at, :string, nullable: true
21
- field :failed_at, :string, nullable: true
22
- field :failure_reason, :string, nullable: true
126
+ field :send_at, :string, nullable: true, wire_name: "sendAt"
127
+ field :created_at, :string, wire_name: "createdAt"
128
+ field :confirmed_at, :string, nullable: true, wire_name: "confirmedAt"
129
+ field :received_at, :string, nullable: true, wire_name: "receivedAt"
130
+ field :read_at, :string, nullable: true, wire_name: "readAt"
131
+ field :played_at, :string, nullable: true, wire_name: "playedAt"
132
+ field :failed_at, :string, nullable: true, wire_name: "failedAt"
133
+ field :failure_reason, :string, nullable: true, wire_name: "failureReason"
134
+ field :secret, :string, nullable: true
135
+ field :link_preview, LinkPreview, optional: true, wire_name: "linkPreview"
23
136
  end
24
137
  end
25
138
  end
@@ -0,0 +1,33 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "base"
4
+
5
+ module Blueticks
6
+ module Types
7
+ NEWSLETTER_VERIFICATIONS = %w[VERIFIED UNVERIFIED].freeze
8
+
9
+ # A WhatsApp newsletter (channel) as returned by create/get
10
+ # (NewsletterResponse). The JID is carried on `newsletter_id`.
11
+ class Newsletter < Base
12
+ field :newsletter_id, :string, wire_name: "newsletterId"
13
+ field :name, :string
14
+ field :description, :string, nullable: true
15
+ field :created_at, :string, nullable: true, wire_name: "createdAt"
16
+ field :subscribers, :integer, nullable: true
17
+ field :invite, :string, nullable: true
18
+ field :verification, [:enum, NEWSLETTER_VERIFICATIONS], nullable: true
19
+ end
20
+
21
+ # A newsletter row in the newsletters.list page (NewsletterListItem). Here
22
+ # the JID is carried on `chat_id`.
23
+ class NewsletterListItem < Base
24
+ field :chat_id, :string, wire_name: "chatId"
25
+ field :name, :string
26
+ field :description, :string, nullable: true
27
+ field :created_at, :string, nullable: true, wire_name: "createdAt"
28
+ field :subscribers, :integer, nullable: true
29
+ field :invite, :string, nullable: true
30
+ field :verification, [:enum, NEWSLETTER_VERIFICATIONS], nullable: true
31
+ end
32
+ end
33
+ end
@@ -0,0 +1,13 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "base"
4
+
5
+ module Blueticks
6
+ module Types
7
+ # Generic `{ "ok": true }` acknowledgement returned by side-effecting
8
+ # endpoints (chat archive/mark-read, message pin/unpin/react).
9
+ class OkResult < Base
10
+ field :ok, [:enum, [true]]
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,70 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "base"
4
+ require_relative "../errors"
5
+
6
+ module Blueticks
7
+ module Types
8
+ # Offset-paginated list envelope. Every v1 list endpoint returns this shape
9
+ # inside the success/data wrapper: `{ data: [...], limit, skip, total }`.
10
+ #
11
+ # Iterate `data` for the current page; advance by passing `skip: skip + limit`
12
+ # (or any offset) back on the next list call. `total` is the full match count
13
+ # across all pages. `has_more` is populated only by endpoints that echo a
14
+ # `hasMore` flag (e.g. messages.list); nil elsewhere.
15
+ class PaginatedPage
16
+ attr_reader :data, :limit, :skip, :total, :has_more
17
+
18
+ def initialize(data:, limit:, skip:, total:, has_more: nil)
19
+ @data = data
20
+ @limit = limit
21
+ @skip = skip
22
+ @total = total
23
+ @has_more = has_more
24
+ end
25
+
26
+ # Build a PaginatedPage from a raw list envelope, decoding each item via
27
+ # item_type.from_hash. Reads `data`/`limit`/`skip`/`total` (and optional
28
+ # `hasMore`) off the envelope; ignores the `success` flag.
29
+ def self.from_hash(raw, item_type:)
30
+ unless raw.is_a?(Hash)
31
+ raise Errors::ValidationError.new(message: "Expected Hash for PaginatedPage, got #{raw.class}")
32
+ end
33
+ unless raw["data"].is_a?(Array)
34
+ raise Errors::ValidationError.new(message: "Field 'data' must be array in PaginatedPage")
35
+ end
36
+
37
+ limit = require_integer(raw, "limit")
38
+ skip = require_integer(raw, "skip")
39
+ total = require_integer(raw, "total")
40
+
41
+ has_more = raw["hasMore"]
42
+ unless has_more.nil? || [true, false].include?(has_more)
43
+ raise Errors::ValidationError.new(message: "Field 'hasMore' must be boolean or absent in PaginatedPage")
44
+ end
45
+
46
+ decoded = raw["data"].each_with_index.map do |item, i|
47
+ item_type.from_hash(item, context: "PaginatedPage.data[#{i}]")
48
+ end
49
+
50
+ new(data: decoded, limit: limit, skip: skip, total: total, has_more: has_more)
51
+ end
52
+
53
+ def self.require_integer(raw, key)
54
+ value = raw[key]
55
+ unless value.is_a?(Integer) && !value.is_a?(TrueClass) && !value.is_a?(FalseClass)
56
+ raise Errors::ValidationError.new(message: "Field '#{key}' must be integer in PaginatedPage")
57
+ end
58
+
59
+ value
60
+ end
61
+
62
+ def to_h
63
+ {
64
+ data: data.map { |d| d.respond_to?(:to_h) ? d.to_h : d },
65
+ limit: limit, skip: skip, total: total, has_more: has_more
66
+ }
67
+ end
68
+ end
69
+ end
70
+ end
@@ -4,11 +4,19 @@ require_relative "base"
4
4
 
5
5
  module Blueticks
6
6
  module Types
7
+ # A connected WhatsApp engine as summarized on the ping response.
8
+ class PingConnection < Base
9
+ field :id, :string
10
+ field :type, [:enum, %w[gateway regular]]
11
+ field :connected, [:enum, [true]]
12
+ end
13
+
7
14
  # Response from GET /v1/ping.
8
15
  class Ping < Base
9
- field :account_id, :string
10
- field :key_prefix, :string
11
- field :scopes, %i[array string]
16
+ field :api, [:enum, %w[ok]]
17
+ field :account_id, :string, wire_name: "accountId"
18
+ field :whatsapp_connections, [:array, PingConnection], wire_name: "whatsappConnections"
19
+ field :message, :string, optional: true
12
20
  end
13
21
  end
14
22
  end
@@ -1,23 +1,37 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require_relative "base"
3
+ require_relative "messages"
4
4
 
5
5
  module Blueticks
6
6
  module Types
7
+ # A queued / scheduled outbound message record — the shape returned by
8
+ # every scheduled_messages.* endpoint. This is a DISTINCT inline schema in
9
+ # the spec (not MessageResponse): only `to`, `type`, `status`, `createdAt`
10
+ # are required. In particular `waMessageKey` is optional and ABSENT on a
11
+ # record that has not dispatched yet, so it must never be decoded through a
12
+ # type that requires it (see Types::Message, used only by messages.create).
7
13
  class ScheduledMessage < Base
8
- field :id, :string
9
- field :to, :string, nullable: true
10
- field :text, :string, nullable: true
11
- field :media_url, :string, nullable: true
12
- field :media_caption, :string, nullable: true
13
- field :media_filename, :string, nullable: true
14
- field :media_mime_type, :string, nullable: true
15
- field :send_at, :string, nullable: true
16
- field :status, :string, nullable: true
17
- field :is_recurring, :boolean
18
- field :recurrence_rule, :string, nullable: true
19
- field :created_at, :string, nullable: true
20
- field :updated_at, :string, nullable: true
14
+ field :id, :string, optional: true
15
+ field :wa_message_key, WaMessageKey, optional: true, nullable: true, wire_name: "waMessageKey"
16
+ field :to, :string
17
+ field :type, [:enum, MESSAGE_KINDS]
18
+ field :text, :string, optional: true, nullable: true
19
+ field :media_url, :string, optional: true, nullable: true, wire_name: "mediaUrl"
20
+ field :media_kind, [:enum, MESSAGE_MEDIA_KINDS], optional: true, nullable: true, wire_name: "mediaKind"
21
+ field :poll_question, :string, optional: true, nullable: true, wire_name: "pollQuestion"
22
+ field :poll_options, %i[array string], optional: true, nullable: true, wire_name: "pollOptions"
23
+ field :poll_allow_multiple, :boolean, optional: true, nullable: true, wire_name: "pollAllowMultiple"
24
+ field :status, [:enum, MESSAGE_STATUSES]
25
+ field :send_at, :string, optional: true, nullable: true, wire_name: "sendAt"
26
+ field :created_at, :string, wire_name: "createdAt"
27
+ field :confirmed_at, :string, optional: true, nullable: true, wire_name: "confirmedAt"
28
+ field :received_at, :string, optional: true, nullable: true, wire_name: "receivedAt"
29
+ field :read_at, :string, optional: true, nullable: true, wire_name: "readAt"
30
+ field :played_at, :string, optional: true, nullable: true, wire_name: "playedAt"
31
+ field :failed_at, :string, optional: true, nullable: true, wire_name: "failedAt"
32
+ field :failure_reason, :string, optional: true, nullable: true, wire_name: "failureReason"
33
+ field :secret, :string, optional: true, nullable: true
34
+ field :link_preview, LinkPreview, optional: true, wire_name: "linkPreview"
21
35
  end
22
36
  end
23
37
  end
@@ -0,0 +1,42 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "base"
4
+
5
+ module Blueticks
6
+ module Types
7
+ SONG_CLIP_STATUSES = %w[submitted queued running streaming complete error].freeze
8
+
9
+ # Suno credit / plan usage (AccountUsageResponse) from GET /v1/suno/account.
10
+ class SunoAccount < Base
11
+ field :credits_left, :number, wire_name: "creditsLeft"
12
+ field :monthly_limit, :number, nullable: true, wire_name: "monthlyLimit"
13
+ field :monthly_usage, :number, nullable: true, wire_name: "monthlyUsage"
14
+ field :plan, :string, nullable: true
15
+ end
16
+
17
+ # A generated song clip (SongClip / GetSongResponse).
18
+ class SongClip < Base
19
+ field :id, :string
20
+ field :status, [:enum, SONG_CLIP_STATUSES]
21
+ field :audio_url, :string, nullable: true, wire_name: "audioUrl"
22
+ field :image_url, :string, nullable: true, wire_name: "imageUrl"
23
+ field :title, :string, nullable: true
24
+ field :duration_sec, :number, nullable: true, wire_name: "durationSec"
25
+ field :model, :string, nullable: true
26
+ field :error_type, :string, nullable: true, optional: true, wire_name: "errorType"
27
+ field :error_message, :string, nullable: true, optional: true, wire_name: "errorMessage"
28
+ end
29
+
30
+ # Result of submitting a song generation job (GenerateSongResponse).
31
+ class GenerateSongResult < Base
32
+ field :job_id, :string, wire_name: "jobId"
33
+ field :clips, [:array, SongClip]
34
+ end
35
+
36
+ # Result of registering a reference-audio upload (CreateUploadResponse).
37
+ class CreateUploadResult < Base
38
+ field :upload_id, :string, wire_name: "uploadId"
39
+ field :status, :string
40
+ end
41
+ end
42
+ end