blueticks 1.1.1 → 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 (37) 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 +31 -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 -30
  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 +21 -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 -71
  22. data/lib/blueticks/types/contacts.rb +6 -7
  23. data/lib/blueticks/types/deleted_resource.rb +14 -0
  24. data/lib/blueticks/types/engines.rb +9 -8
  25. data/lib/blueticks/types/groups.rb +22 -6
  26. data/lib/blueticks/types/messages.rb +124 -11
  27. data/lib/blueticks/types/newsletter.rb +33 -0
  28. data/lib/blueticks/types/ok_result.rb +13 -0
  29. data/lib/blueticks/types/paginated.rb +70 -0
  30. data/lib/blueticks/types/ping.rb +11 -3
  31. data/lib/blueticks/types/scheduled_messages.rb +28 -14
  32. data/lib/blueticks/types/suno.rb +42 -0
  33. data/lib/blueticks/types/utils.rb +5 -12
  34. data/lib/blueticks/types/webhooks.rb +13 -11
  35. data/lib/blueticks/version.rb +1 -1
  36. data/lib/blueticks.rb +2 -2
  37. metadata +8 -1
@@ -2,64 +2,90 @@
2
2
 
3
3
  require_relative "../base_resource"
4
4
  require_relative "../types/groups"
5
+ require_relative "../types/paginated"
5
6
 
6
7
  module Blueticks
7
8
  module Resources
8
9
  class GroupsResource < BaseResource
10
+ # List groups.
11
+ #
12
+ # List the groups the connected WhatsApp engine sees. Offset-paginated
13
+ # (GroupListItem rows), with an optional case-insensitive name search via
14
+ # `search_token`.
15
+ def list(search_token: nil, include_archive: nil, skip: nil, limit: nil)
16
+ params = {}
17
+ params["searchToken"] = search_token unless search_token.nil?
18
+ params["includeArchive"] = include_archive unless include_archive.nil?
19
+ params["skip"] = skip unless skip.nil?
20
+ params["limit"] = limit unless limit.nil?
21
+ env = client.request("GET", "/v1/groups", params: params.empty? ? nil : params)
22
+ Types::PaginatedPage.from_hash(env, item_type: Types::GroupListItem)
23
+ end
24
+
9
25
  # Create a new group with an initial participant list.
10
26
  def create(name:, participants:)
11
- data = client.request("POST", "/v1/groups",
12
- body: { "name" => name, "participants" => participants })
13
- Types::Group.from_hash(data)
27
+ env = client.request("POST", "/v1/groups",
28
+ body: { "name" => name, "participants" => participants })
29
+ Types::Group.from_hash(env && env["data"])
14
30
  end
15
31
 
16
- # Retrieve group metadata by JID.
17
- def get(group_id)
18
- data = client.request("GET", "/v1/groups/#{group_id}")
19
- Types::Group.from_hash(data)
32
+ # Retrieve group metadata by id. Pass `include: "participants"` to embed
33
+ # the participant list.
34
+ def retrieve(group_id, include: nil)
35
+ params = include.nil? ? nil : { "include" => include }
36
+ env = client.request("GET", "/v1/groups/#{group_id}", params: params)
37
+ Types::Group.from_hash(env && env["data"])
20
38
  end
21
39
 
22
- # Rename the group and/or update admin-only settings.
40
+ # Rename the group and/or update admin-only settings. `settings` is a hash
41
+ # using the API's wire keys (announce, restrict, editInfoAdminsOnly,
42
+ # description).
23
43
  def update(group_id, name: nil, settings: nil)
24
44
  body = {}
25
45
  body["name"] = name unless name.nil?
26
46
  body["settings"] = settings unless settings.nil?
27
- data = client.request("PATCH", "/v1/groups/#{group_id}", body: body)
28
- Types::Group.from_hash(data)
47
+ env = client.request("PATCH", "/v1/groups/#{group_id}", body: body)
48
+ Types::Group.from_hash(env && env["data"])
29
49
  end
30
50
 
31
- # Add a participant to the group by chat_id (JID) or +E.164 phone.
32
- def add_member(group_id, chat_id:)
33
- data = client.request("POST", "/v1/groups/#{group_id}/members",
34
- body: { "chat_id" => chat_id })
35
- Types::Group.from_hash(data)
51
+ # Add members to the group by chat id (JID / +E.164) and/or a list of
52
+ # participants.
53
+ def add_members(group_id, chat_id: nil, participants: nil)
54
+ body = {}
55
+ body["chatId"] = chat_id unless chat_id.nil?
56
+ body["participants"] = participants unless participants.nil?
57
+ env = client.request("POST", "/v1/groups/#{group_id}/members", body: body)
58
+ Types::Group.from_hash(env && env["data"])
36
59
  end
37
60
 
38
61
  # Remove a participant from the group.
39
62
  def remove_member(group_id, chat_id)
40
- data = client.request("DELETE", "/v1/groups/#{group_id}/members/#{chat_id}")
41
- Types::Group.from_hash(data)
63
+ env = client.request("DELETE", "/v1/groups/#{group_id}/members/#{chat_id}")
64
+ Types::Group.from_hash(env && env["data"])
42
65
  end
43
66
 
44
67
  # Grant admin privileges to a group member.
45
68
  def promote_admin(group_id, chat_id)
46
- data = client.request("POST", "/v1/groups/#{group_id}/members/#{chat_id}/admin")
47
- Types::Group.from_hash(data)
69
+ env = client.request("POST", "/v1/groups/#{group_id}/members/#{chat_id}/admin")
70
+ Types::Group.from_hash(env && env["data"])
48
71
  end
49
72
 
50
73
  # Revoke admin privileges from a group member.
51
74
  def demote_admin(group_id, chat_id)
52
- data = client.request("DELETE", "/v1/groups/#{group_id}/members/#{chat_id}/admin")
53
- Types::Group.from_hash(data)
75
+ env = client.request("DELETE", "/v1/groups/#{group_id}/members/#{chat_id}/admin")
76
+ Types::Group.from_hash(env && env["data"])
54
77
  end
55
78
 
56
- # Replace the group picture. `file_data_url` is a base64 data URL (PNG/JPEG, <=20 MiB).
57
- def set_picture(group_id, file_data_url:, file_name: nil, file_mime_type: nil)
58
- body = { "file_data_url" => file_data_url }
59
- body["file_name"] = file_name unless file_name.nil?
60
- body["file_mime_type"] = file_mime_type unless file_mime_type.nil?
61
- data = client.request("PUT", "/v1/groups/#{group_id}/picture", body: body)
62
- Types::Group.from_hash(data)
79
+ # Replace the group picture. `file_data_url` is a base64 data URL, or pass
80
+ # a hosted `url`.
81
+ def set_picture(group_id, file_data_url: nil, url: nil, file_name: nil, file_mime_type: nil)
82
+ body = {}
83
+ body["fileDataUrl"] = file_data_url unless file_data_url.nil?
84
+ body["url"] = url unless url.nil?
85
+ body["fileName"] = file_name unless file_name.nil?
86
+ body["fileMimeType"] = file_mime_type unless file_mime_type.nil?
87
+ env = client.request("PUT", "/v1/groups/#{group_id}/picture", body: body)
88
+ Types::Group.from_hash(env && env["data"])
63
89
  end
64
90
 
65
91
  # Leave the group as the authenticated identity. Idempotent (204).
@@ -2,46 +2,125 @@
2
2
 
3
3
  require_relative "../base_resource"
4
4
  require_relative "../types/messages"
5
- require_relative "../types/page"
5
+ require_relative "../types/ok_result"
6
+ require_relative "../types/paginated"
6
7
 
7
8
  module Blueticks
8
9
  module Resources
9
10
  class MessagesResource < BaseResource
10
- # Send a message immediately, or schedule one for later with `send_at:`.
11
- #
12
- # @param to [String] WhatsApp chat-id or phone number
13
- # @param text [String, nil] Plain-text message body
14
- # @param media_url [String, nil] Public URL to media to attach
15
- # @param media_caption [String, nil] Caption for media
16
- # @param send_at [String, nil] ISO 8601 timestamp to schedule
17
- # @param from [String, nil] Multi-number engine identifier
18
- # @param idempotency_key [String, nil] Optional idempotency key
19
- # @return [Blueticks::Types::Message]
20
- def send(to:, text: nil, media_url: nil, media_caption: nil, send_at: nil,
21
- from: nil, idempotency_key: nil)
22
- body = { "to" => to }
23
- body["text"] = text unless text.nil?
24
- body["media_url"] = media_url unless media_url.nil?
25
- body["media_caption"] = media_caption unless media_caption.nil?
26
- body["send_at"] = send_at unless send_at.nil?
27
- body["from"] = from unless from.nil?
28
- data = client.request("POST", "/v1/messages", body: body, idempotency_key: idempotency_key)
29
- Types::Message.from_hash(data)
11
+ # Query messages from the WhatsApp store, newest first. Offset-paginated
12
+ # (PublicMessage rows). `message_types` is an Array of wire message types
13
+ # (e.g. `%w[chat image]`).
14
+ def list(chat_id: nil, search_token: nil, order: nil, since: nil, until_: nil,
15
+ message_types: nil, load_from_phone_if_needed: nil,
16
+ include_media_content: nil, skip: nil, limit: nil)
17
+ params = {}
18
+ params["chatId"] = chat_id unless chat_id.nil?
19
+ params["searchToken"] = search_token unless search_token.nil?
20
+ params["order"] = order unless order.nil?
21
+ params["since"] = since unless since.nil?
22
+ params["until"] = until_ unless until_.nil?
23
+ params["messageTypes"] = message_types.join(",") if message_types && !message_types.empty?
24
+ params["loadFromPhoneIfNeeded"] = load_from_phone_if_needed unless load_from_phone_if_needed.nil?
25
+ params["includeMediaContent"] = include_media_content unless include_media_content.nil?
26
+ params["skip"] = skip unless skip.nil?
27
+ params["limit"] = limit unless limit.nil?
28
+ env = client.request("GET", "/v1/messages", params: params.empty? ? nil : params)
29
+ Types::PaginatedPage.from_hash(env, item_type: Types::PublicMessage)
30
30
  end
31
31
 
32
- # Retrieve a single message by id.
33
- def get(message_id)
34
- data = client.request("GET", "/v1/messages/#{message_id}")
35
- Types::Message.from_hash(data)
32
+ # Retrieve a single store message by its serialized wire key. Pass
33
+ # `chat_id` to scope the lookup.
34
+ def retrieve(wa_message_key, chat_id: nil)
35
+ params = chat_id.nil? ? nil : { "chatId" => chat_id }
36
+ env = client.request("GET", "/v1/messages/#{wa_message_key}", params: params)
37
+ Types::PublicMessage.from_hash(env && env["data"])
36
38
  end
37
39
 
38
- # List messages sent through the API, newest first. Cursor-paginated.
39
- def list(limit: nil, cursor: nil)
40
+ # Get delivery acknowledgement for a single message.
41
+ def get_ack(wa_message_key, chat_id: nil)
42
+ params = chat_id.nil? ? nil : { "chatId" => chat_id }
43
+ env = client.request("GET", "/v1/messages/ack/#{wa_message_key}", params: params)
44
+ Types::MessageAck.from_hash(env && env["data"])
45
+ end
46
+
47
+ # Batch get delivery status for up to 200 sent messages. Offset-paginated
48
+ # (BatchAckEntry rows).
49
+ def batch_acks(message_keys:, chat_id: nil)
50
+ body = { "messageKeys" => message_keys }
51
+ body["chatId"] = chat_id unless chat_id.nil?
52
+ env = client.request("POST", "/v1/messages/acks", body: body)
53
+ Types::PaginatedPage.from_hash(env, item_type: Types::BatchAckEntry)
54
+ end
55
+
56
+ # Ask the engine to pull older history from the connected phone.
57
+ def load_older(chat_id)
58
+ env = client.request("POST", "/v1/messages/load_older/#{chat_id}")
59
+ Types::LoadOlderResult.from_hash(env && env["data"])
60
+ end
61
+
62
+ # Fetch media (URL and/or base64 bytes) for a message.
63
+ def get_media(wa_message_key, chat_id: nil, max_attempts: nil)
40
64
  params = {}
41
- params["limit"] = limit unless limit.nil?
42
- params["cursor"] = cursor unless cursor.nil?
43
- data = client.request("GET", "/v1/messages", params: params.empty? ? nil : params)
44
- Types::Page.from_hash(data, item_type: Types::Message)
65
+ params["chatId"] = chat_id unless chat_id.nil?
66
+ params["maxAttempts"] = max_attempts unless max_attempts.nil?
67
+ env = client.request("GET", "/v1/messages/media/#{wa_message_key}",
68
+ params: params.empty? ? nil : params)
69
+ Types::Media.from_hash(env && env["data"])
70
+ end
71
+
72
+ # Pin a message. Optional `duration` (seconds) sets the pin TTL.
73
+ def pin(wa_message_key, chat_id: nil, duration: nil)
74
+ params = chat_id.nil? ? nil : { "chatId" => chat_id }
75
+ body = {}
76
+ body["duration"] = duration unless duration.nil?
77
+ env = client.request("POST", "/v1/messages/pin/#{wa_message_key}", params: params, body: body)
78
+ Types::OkResult.from_hash(env && env["data"])
79
+ end
80
+
81
+ # Unpin a message.
82
+ def unpin(wa_message_key, chat_id: nil)
83
+ params = chat_id.nil? ? nil : { "chatId" => chat_id }
84
+ env = client.request("POST", "/v1/messages/unpin/#{wa_message_key}", params: params)
85
+ Types::OkResult.from_hash(env && env["data"])
86
+ end
87
+
88
+ # List the pinned messages in a chat. Offset-paginated (PinnedMessage rows).
89
+ def pinned(chat_id)
90
+ env = client.request("GET", "/v1/messages/pinned/#{chat_id}")
91
+ Types::PaginatedPage.from_hash(env, item_type: Types::PinnedMessage)
92
+ end
93
+
94
+ # React to a message. Pass an empty `emoji` string to remove a reaction.
95
+ def react(wa_message_key, emoji:, chat_id: nil)
96
+ params = chat_id.nil? ? nil : { "chatId" => chat_id }
97
+ env = client.request("POST", "/v1/messages/reactions/#{wa_message_key}",
98
+ params: params, body: { "emoji" => emoji })
99
+ Types::OkResult.from_hash(env && env["data"])
100
+ end
101
+
102
+ # Send a message immediately to a chat (direct dispatch — no queue row is
103
+ # persisted for the wait; the response carries the WhatsApp wire key). The
104
+ # body is a discriminated union keyed on `type` (`text`, `media`, `poll`).
105
+ def create(chat_id, type:, text: nil, media_url: nil, media_base64: nil,
106
+ media_kind: nil, media_filename: nil, poll_question: nil,
107
+ poll_options: nil, poll_allow_multiple: nil, reply_to: nil,
108
+ secret: nil, with_typing: nil, typing_seconds: nil, idempotency_key: nil)
109
+ body = { "type" => type }
110
+ body["text"] = text unless text.nil?
111
+ body["mediaUrl"] = media_url unless media_url.nil?
112
+ body["mediaBase64"] = media_base64 unless media_base64.nil?
113
+ body["mediaKind"] = media_kind unless media_kind.nil?
114
+ body["mediaFilename"] = media_filename unless media_filename.nil?
115
+ body["pollQuestion"] = poll_question unless poll_question.nil?
116
+ body["pollOptions"] = poll_options unless poll_options.nil?
117
+ body["pollAllowMultiple"] = poll_allow_multiple unless poll_allow_multiple.nil?
118
+ body["replyTo"] = reply_to unless reply_to.nil?
119
+ body["secret"] = secret unless secret.nil?
120
+ body["withTyping"] = with_typing unless with_typing.nil?
121
+ body["typingSeconds"] = typing_seconds unless typing_seconds.nil?
122
+ env = client.request("POST", "/v1/messages/#{chat_id}", body: body, idempotency_key: idempotency_key)
123
+ Types::Message.from_hash(env && env["data"])
45
124
  end
46
125
  end
47
126
  end
@@ -0,0 +1,43 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "../base_resource"
4
+ require_relative "../types/newsletter"
5
+ require_relative "../types/paginated"
6
+
7
+ module Blueticks
8
+ module Resources
9
+ class NewslettersResource < BaseResource
10
+ # List newsletters.
11
+ #
12
+ # List newsletters visible to the connected WhatsApp engine. Offset-paginated
13
+ # (NewsletterListItem rows). Requires `newsletters:read` scope.
14
+ def list(search_token: nil, include_archive: nil, skip: nil, limit: nil)
15
+ params = {}
16
+ params["searchToken"] = search_token unless search_token.nil?
17
+ params["includeArchive"] = include_archive unless include_archive.nil?
18
+ params["skip"] = skip unless skip.nil?
19
+ params["limit"] = limit unless limit.nil?
20
+ env = client.request("GET", "/v1/newsletters", params: params.empty? ? nil : params)
21
+ Types::PaginatedPage.from_hash(env, item_type: Types::NewsletterListItem)
22
+ end
23
+
24
+ # Create newsletter.
25
+ #
26
+ # Create a new WhatsApp newsletter (channel). Requires `messages:write` scope.
27
+ def create(name:, description: nil)
28
+ body = { "name" => name }
29
+ body["description"] = description unless description.nil?
30
+ env = client.request("POST", "/v1/newsletters", body: body)
31
+ Types::Newsletter.from_hash(env && env["data"])
32
+ end
33
+
34
+ # Get newsletter.
35
+ #
36
+ # Retrieve a newsletter by its JID. Requires `newsletters:read` scope.
37
+ def retrieve(newsletter_id)
38
+ env = client.request("GET", "/v1/newsletters/#{newsletter_id}")
39
+ Types::Newsletter.from_hash(env && env["data"])
40
+ end
41
+ end
42
+ end
43
+ end
@@ -6,10 +6,13 @@ require_relative "../types/ping"
6
6
  module Blueticks
7
7
  module Resources
8
8
  class PingResource < BaseResource
9
- # Health check. Returns basic info about the authenticated API key.
9
+ # Ping.
10
+ #
11
+ # Probe the API: returns `api: "ok"`, the account id, and the currently
12
+ # connected WhatsApp engines. Useful as a connection/liveness test.
10
13
  def retrieve
11
- data = client.request("GET", "/v1/ping")
12
- Types::Ping.from_hash(data)
14
+ env = client.request("GET", "/v1/ping")
15
+ Types::Ping.from_hash(env && env["data"])
13
16
  end
14
17
  end
15
18
  end
@@ -2,49 +2,93 @@
2
2
 
3
3
  require_relative "../base_resource"
4
4
  require_relative "../types/scheduled_messages"
5
- require_relative "../types/page"
5
+ require_relative "../types/deleted_resource"
6
+ require_relative "../types/paginated"
6
7
 
7
8
  module Blueticks
8
9
  module Resources
9
10
  class ScheduledMessagesResource < BaseResource
10
- # List scheduled messages.
11
+ # Send / schedule a message to a chat.
11
12
  #
12
- # Retrieves a list of all resources from the service. Cursor-paginated.
13
- def list(limit: nil, cursor: nil)
14
- params = {}
15
- params["limit"] = limit unless limit.nil?
16
- params["cursor"] = cursor unless cursor.nil?
17
- data = client.request("GET", "/v1/scheduled-messages", params: params.empty? ? nil : params)
18
- Types::Page.from_hash(data, item_type: Types::ScheduledMessage)
13
+ # Queue a message for delivery to `chat_id` (a WhatsApp JID or phone). The
14
+ # body is a discriminated union keyed on `type` (`text`, `media`, `poll`).
15
+ # Omit `send_at` to dispatch as soon as possible, or pass an ISO-8601
16
+ # timestamp to schedule it.
17
+ #
18
+ # - `type: "text"` — requires `text`.
19
+ # - `type: "media"` — requires `media_url` or `media_base64`; optional
20
+ # `media_kind`, `media_filename`.
21
+ # - `type: "poll"` — requires `poll_question` and `poll_options`; optional
22
+ # `poll_allow_multiple`.
23
+ def create(chat_id, type:, text: nil, media_url: nil, media_base64: nil,
24
+ media_kind: nil, media_filename: nil, poll_question: nil,
25
+ poll_options: nil, poll_allow_multiple: nil, reply_to: nil,
26
+ secret: nil, send_at: nil, idempotency_key: nil)
27
+ body = { "type" => type }
28
+ body["text"] = text unless text.nil?
29
+ body["mediaUrl"] = media_url unless media_url.nil?
30
+ body["mediaBase64"] = media_base64 unless media_base64.nil?
31
+ body["mediaKind"] = media_kind unless media_kind.nil?
32
+ body["mediaFilename"] = media_filename unless media_filename.nil?
33
+ body["pollQuestion"] = poll_question unless poll_question.nil?
34
+ body["pollOptions"] = poll_options unless poll_options.nil?
35
+ body["pollAllowMultiple"] = poll_allow_multiple unless poll_allow_multiple.nil?
36
+ body["replyTo"] = reply_to unless reply_to.nil?
37
+ body["secret"] = secret unless secret.nil?
38
+ body["sendAt"] = send_at unless send_at.nil?
39
+ env = client.request("POST", "/v1/scheduled-messages/#{chat_id}", body: body,
40
+ idempotency_key: idempotency_key)
41
+ Types::ScheduledMessage.from_hash(env && env["data"])
19
42
  end
20
43
 
21
- # Get scheduled message.
22
- #
23
- # Retrieves a single resource with the given id from the service.
24
- def retrieve(id)
25
- data = client.request("GET", "/v1/scheduled-messages/#{id}")
26
- Types::ScheduledMessage.from_hash(data)
44
+ # Get the current status of a queued/scheduled message by id.
45
+ def retrieve(message_id)
46
+ env = client.request("GET", "/v1/scheduled-messages/#{message_id}")
47
+ Types::ScheduledMessage.from_hash(env && env["data"])
27
48
  end
28
49
 
29
- # Update scheduled message.
30
- #
31
- # Updates the resource identified by id using data.
32
- def update(id, text: nil, media_url: nil, media_caption: nil, send_at: nil)
50
+ # List messages in the user-messages queue (all sources), newest first.
51
+ # Offset-paginated. Optionally filter by `chat_id` and/or lifecycle
52
+ # `status`.
53
+ def list(chat_id: nil, search_token: nil, status: nil, order: nil, skip: nil, limit: nil)
54
+ params = {}
55
+ params["chatId"] = chat_id unless chat_id.nil?
56
+ params["searchToken"] = search_token unless search_token.nil?
57
+ params["status"] = status unless status.nil?
58
+ params["order"] = order unless order.nil?
59
+ params["skip"] = skip unless skip.nil?
60
+ params["limit"] = limit unless limit.nil?
61
+ env = client.request("GET", "/v1/scheduled-messages", params: params.empty? ? nil : params)
62
+ Types::PaginatedPage.from_hash(env, item_type: Types::ScheduledMessage)
63
+ end
64
+
65
+ # Edit a previously-queued message that has not dispatched yet. Pass any
66
+ # subset of the editable fields.
67
+ def update(message_id, type: nil, text: nil, media_url: nil, media_base64: nil,
68
+ media_kind: nil, media_filename: nil, poll_question: nil,
69
+ poll_options: nil, poll_allow_multiple: nil, reply_to: nil,
70
+ secret: nil, send_at: nil)
33
71
  body = {}
72
+ body["type"] = type unless type.nil?
34
73
  body["text"] = text unless text.nil?
35
- body["media_url"] = media_url unless media_url.nil?
36
- body["media_caption"] = media_caption unless media_caption.nil?
37
- body["send_at"] = send_at unless send_at.nil?
38
- data = client.request("PATCH", "/v1/scheduled-messages/#{id}", body: body)
39
- Types::ScheduledMessage.from_hash(data)
74
+ body["mediaUrl"] = media_url unless media_url.nil?
75
+ body["mediaBase64"] = media_base64 unless media_base64.nil?
76
+ body["mediaKind"] = media_kind unless media_kind.nil?
77
+ body["mediaFilename"] = media_filename unless media_filename.nil?
78
+ body["pollQuestion"] = poll_question unless poll_question.nil?
79
+ body["pollOptions"] = poll_options unless poll_options.nil?
80
+ body["pollAllowMultiple"] = poll_allow_multiple unless poll_allow_multiple.nil?
81
+ body["replyTo"] = reply_to unless reply_to.nil?
82
+ body["secret"] = secret unless secret.nil?
83
+ body["sendAt"] = send_at unless send_at.nil?
84
+ env = client.request("PATCH", "/v1/scheduled-messages/#{message_id}", body: body)
85
+ Types::ScheduledMessage.from_hash(env && env["data"])
40
86
  end
41
87
 
42
- # Cancel scheduled message.
43
- #
44
- # Removes the resource with id.
45
- def delete(id)
46
- data = client.request("DELETE", "/v1/scheduled-messages/#{id}")
47
- Types::ScheduledMessage.from_hash(data)
88
+ # Delete (cancel) a queued/scheduled message by id.
89
+ def delete(message_id)
90
+ env = client.request("DELETE", "/v1/scheduled-messages/#{message_id}")
91
+ Types::DeletedResource.from_hash(env && env["data"])
48
92
  end
49
93
  end
50
94
  end
@@ -0,0 +1,56 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "../base_resource"
4
+ require_relative "../types/suno"
5
+
6
+ module Blueticks
7
+ module Resources
8
+ class SunoResource < BaseResource
9
+ # Get the Suno credit / plan usage for the workspace.
10
+ def account
11
+ env = client.request("GET", "/v1/suno/account")
12
+ Types::SunoAccount.from_hash(env && env["data"])
13
+ end
14
+
15
+ # Submit a song generation job. Requires `lyrics` and `style`; the many
16
+ # optional knobs mirror the Suno request (vocal_gender, weirdness,
17
+ # style_influence, audio_influence, instrumental, model, title, upload_id,
18
+ # captcha_token, negative_style).
19
+ def create_song(lyrics:, style:, negative_style: nil, vocal_gender: nil,
20
+ weirdness: nil, style_influence: nil, audio_influence: nil,
21
+ instrumental: nil, model: nil, title: nil, upload_id: nil,
22
+ captcha_token: nil)
23
+ body = { "lyrics" => lyrics, "style" => style }
24
+ body["negativeStyle"] = negative_style unless negative_style.nil?
25
+ body["vocalGender"] = vocal_gender unless vocal_gender.nil?
26
+ body["weirdness"] = weirdness unless weirdness.nil?
27
+ body["styleInfluence"] = style_influence unless style_influence.nil?
28
+ body["audioInfluence"] = audio_influence unless audio_influence.nil?
29
+ body["instrumental"] = instrumental unless instrumental.nil?
30
+ body["model"] = model unless model.nil?
31
+ body["title"] = title unless title.nil?
32
+ body["uploadId"] = upload_id unless upload_id.nil?
33
+ body["captchaToken"] = captcha_token unless captcha_token.nil?
34
+ env = client.request("POST", "/v1/suno/songs", body: body)
35
+ Types::GenerateSongResult.from_hash(env && env["data"])
36
+ end
37
+
38
+ # Get a generated song (clip) by id.
39
+ def get_song(song_id)
40
+ env = client.request("GET", "/v1/suno/songs/#{song_id}")
41
+ Types::SongClip.from_hash(env && env["data"])
42
+ end
43
+
44
+ # Register a reference-audio upload (by URL or base64) for use as an
45
+ # influence on a subsequent song generation.
46
+ def create_upload(audio_url: nil, audio_base64: nil, file_name: nil)
47
+ body = {}
48
+ body["audioUrl"] = audio_url unless audio_url.nil?
49
+ body["audioBase64"] = audio_base64 unless audio_base64.nil?
50
+ body["fileName"] = file_name unless file_name.nil?
51
+ env = client.request("POST", "/v1/suno/uploads", body: body)
52
+ Types::CreateUploadResult.from_hash(env && env["data"])
53
+ end
54
+ end
55
+ end
56
+ end
@@ -1,23 +1,13 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require_relative "../base_resource"
4
- require_relative "../types/utils"
5
4
 
6
5
  module Blueticks
7
6
  module Resources
7
+ # UtilsResource had methods for /v1/utils/* which are not in the current spec.
8
+ # The accessor is kept for backward compatibility with test_client.rb.
9
+ # No public methods are exposed.
8
10
  class UtilsResource < BaseResource
9
- # Validate a phone number or chat-id; returns the engine's canonical form.
10
- def validate_phone(phone_or_chat_id:)
11
- data = client.request("POST", "/v1/utils/validate_phone",
12
- body: { "phone_or_chat_id" => phone_or_chat_id })
13
- Types::PhoneValidation.from_hash(data)
14
- end
15
-
16
- # Fetch OpenGraph-style metadata for a URL (uses the engine's renderer).
17
- def link_preview(url:)
18
- data = client.request("GET", "/v1/utils/link_preview", params: { "url" => url })
19
- Types::LinkPreview.from_hash(data)
20
- end
21
11
  end
22
12
  end
23
13
  end
@@ -1,51 +1,52 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require_relative "../base_resource"
4
+ require_relative "../types/deleted_resource"
4
5
  require_relative "../types/webhooks"
5
- require_relative "../types/page"
6
+ require_relative "../types/paginated"
6
7
 
7
8
  module Blueticks
8
9
  module Resources
9
10
  class WebhooksResource < BaseResource
11
+ # Create a webhook subscription.
10
12
  def create(url:, events:, description: nil)
11
13
  body = { "url" => url, "events" => events }
12
14
  body["description"] = description unless description.nil?
13
- data = client.request("POST", "/v1/webhooks", body: body)
14
- Types::WebhookCreateResult.from_hash(data)
15
+ env = client.request("POST", "/v1/webhooks", body: body)
16
+ Types::Webhook.from_hash(env && env["data"])
15
17
  end
16
18
 
17
- # List webhooks, newest first. Cursor-paginated.
18
- def list(limit: nil, cursor: nil)
19
+ # List webhooks, newest first. Offset-paginated.
20
+ def list(order: nil, skip: nil, limit: nil)
19
21
  params = {}
22
+ params["order"] = order unless order.nil?
23
+ params["skip"] = skip unless skip.nil?
20
24
  params["limit"] = limit unless limit.nil?
21
- params["cursor"] = cursor unless cursor.nil?
22
- data = client.request("GET", "/v1/webhooks", params: params.empty? ? nil : params)
23
- Types::Page.from_hash(data, item_type: Types::Webhook)
25
+ env = client.request("GET", "/v1/webhooks", params: params.empty? ? nil : params)
26
+ Types::PaginatedPage.from_hash(env, item_type: Types::Webhook)
24
27
  end
25
28
 
26
- def get(webhook_id)
27
- data = client.request("GET", "/v1/webhooks/#{webhook_id}")
28
- Types::Webhook.from_hash(data)
29
+ # Retrieve a webhook by id.
30
+ def retrieve(webhook_id)
31
+ env = client.request("GET", "/v1/webhooks/#{webhook_id}")
32
+ Types::Webhook.from_hash(env && env["data"])
29
33
  end
30
34
 
35
+ # Update a webhook's url, events, description and/or status.
31
36
  def update(webhook_id, url: nil, events: nil, description: nil, status: nil)
32
37
  body = {}
33
38
  body["url"] = url unless url.nil?
34
39
  body["events"] = events unless events.nil?
35
40
  body["description"] = description unless description.nil?
36
41
  body["status"] = status unless status.nil?
37
- data = client.request("PATCH", "/v1/webhooks/#{webhook_id}", body: body)
38
- Types::Webhook.from_hash(data)
42
+ env = client.request("PATCH", "/v1/webhooks/#{webhook_id}", body: body)
43
+ Types::Webhook.from_hash(env && env["data"])
39
44
  end
40
45
 
46
+ # Delete a webhook.
41
47
  def delete(webhook_id)
42
- client.request("DELETE", "/v1/webhooks/#{webhook_id}")
43
- nil
44
- end
45
-
46
- def rotate_secret(webhook_id)
47
- data = client.request("POST", "/v1/webhooks/#{webhook_id}/rotate-secret")
48
- Types::WebhookCreateResult.from_hash(data)
48
+ env = client.request("DELETE", "/v1/webhooks/#{webhook_id}")
49
+ Types::DeletedResource.from_hash(env && env["data"])
49
50
  end
50
51
  end
51
52
  end
@@ -8,8 +8,9 @@ module Blueticks
8
8
  class Account < Base
9
9
  field :id, :string
10
10
  field :name, :string
11
+ field :user_email, :string, nullable: true, wire_name: "userEmail"
11
12
  field :timezone, :string, nullable: true
12
- field :created_at, :string
13
+ field :created_at, :string, wire_name: "createdAt"
13
14
  end
14
15
  end
15
16
  end