unisender-ruby 0.1.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 +7 -0
- data/CHANGELOG.md +25 -0
- data/CONTRIBUTING.md +43 -0
- data/LICENSE.txt +21 -0
- data/README.md +140 -0
- data/docs/api.md +1063 -0
- data/examples/campaign.rb +13 -0
- data/examples/check_connection.rb +16 -0
- data/examples/events.rb +20 -0
- data/examples/export.rb +11 -0
- data/examples/import.rb +18 -0
- data/examples/offline.rb +26 -0
- data/examples/quickstart.rb +6 -0
- data/examples/subscribe.rb +12 -0
- data/lib/unisender/batches.rb +31 -0
- data/lib/unisender/client.rb +59 -0
- data/lib/unisender/errors.rb +129 -0
- data/lib/unisender/event_payload.rb +63 -0
- data/lib/unisender/events.rb +19 -0
- data/lib/unisender/form.rb +67 -0
- data/lib/unisender/helpers.rb +28 -0
- data/lib/unisender/operations/contacts_1.rb +51 -0
- data/lib/unisender/operations/contacts_2.rb +44 -0
- data/lib/unisender/operations/inputs_1.rb +48 -0
- data/lib/unisender/operations/messages_1.rb +58 -0
- data/lib/unisender/operations/messages_2.rb +54 -0
- data/lib/unisender/operations/messages_3.rb +12 -0
- data/lib/unisender/operations/notes_1.rb +38 -0
- data/lib/unisender/operations/statistics_1.rb +48 -0
- data/lib/unisender/operations/statistics_2.rb +18 -0
- data/lib/unisender/operations/templates_1.rb +45 -0
- data/lib/unisender/paginated_operations.rb +42 -0
- data/lib/unisender/pagination.rb +68 -0
- data/lib/unisender/protocol.rb +46 -0
- data/lib/unisender/response.rb +64 -0
- data/lib/unisender/retry_policy.rb +82 -0
- data/lib/unisender/task_waiter.rb +52 -0
- data/lib/unisender/transport.rb +53 -0
- data/lib/unisender/validation.rb +72 -0
- data/lib/unisender/version.rb +5 -0
- data/lib/unisender.rb +29 -0
- data/sig/operations.rbs +78 -0
- data/sig/unisender.rbs +218 -0
- metadata +87 -0
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Unisender
|
|
4
|
+
# Polls an existing asynchronous job without starting or downloading an export.
|
|
5
|
+
class TaskWaiter
|
|
6
|
+
PENDING = %w[new processing].freeze
|
|
7
|
+
|
|
8
|
+
def initialize(client, clock:, sleeper:)
|
|
9
|
+
@client = client
|
|
10
|
+
@clock = clock
|
|
11
|
+
@sleeper = sleeper
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
def call(task_uuid, interval, timeout)
|
|
15
|
+
validate!(interval, timeout)
|
|
16
|
+
deadline = @clock.call + timeout
|
|
17
|
+
loop do
|
|
18
|
+
response = @client.get_task_result(task_uuid:)
|
|
19
|
+
return response if complete?(response)
|
|
20
|
+
|
|
21
|
+
wait(interval, deadline)
|
|
22
|
+
end
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
private
|
|
26
|
+
|
|
27
|
+
def validate!(interval, timeout)
|
|
28
|
+
[interval, timeout].each do |value|
|
|
29
|
+
unless value.is_a?(Numeric) && value.positive? && value.finite?
|
|
30
|
+
raise ArgumentError, "Polling interval and timeout must be positive and finite"
|
|
31
|
+
end
|
|
32
|
+
end
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
def complete?(response)
|
|
36
|
+
result = response.result
|
|
37
|
+
raise TaskError, response unless result.is_a?(Hash)
|
|
38
|
+
return true if result["status"] == "completed"
|
|
39
|
+
raise TaskError, response unless PENDING.include?(result["status"])
|
|
40
|
+
|
|
41
|
+
false
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
def wait(interval, deadline)
|
|
45
|
+
remaining = deadline - @clock.call
|
|
46
|
+
raise TimeoutError, "Timed out waiting for the UniSender task" unless remaining.positive?
|
|
47
|
+
|
|
48
|
+
@sleeper.call([interval, remaining].min)
|
|
49
|
+
raise TimeoutError, "Timed out waiting for the UniSender task" if @clock.call >= deadline
|
|
50
|
+
end
|
|
51
|
+
end
|
|
52
|
+
end
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "net/http"
|
|
4
|
+
require "openssl"
|
|
5
|
+
|
|
6
|
+
module Unisender
|
|
7
|
+
# Verified HTTPS transport with explicit timeouts and no implicit retries.
|
|
8
|
+
class Transport
|
|
9
|
+
def initialize(open_timeout: 5, read_timeout: 60, write_timeout: 30, http_factory: nil)
|
|
10
|
+
[open_timeout, read_timeout, write_timeout].each do |timeout|
|
|
11
|
+
unless timeout.is_a?(Numeric) && timeout.positive? && timeout.finite?
|
|
12
|
+
raise ArgumentError, "Timeouts must be positive finite numbers"
|
|
13
|
+
end
|
|
14
|
+
end
|
|
15
|
+
@open_timeout = open_timeout
|
|
16
|
+
@read_timeout = read_timeout
|
|
17
|
+
@write_timeout = write_timeout
|
|
18
|
+
@http_factory = http_factory || ->(uri) { Net::HTTP.new(uri.hostname.to_s, uri.port) }
|
|
19
|
+
freeze
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
def post(uri, headers:, body:)
|
|
23
|
+
exchange(uri, headers:, body:)
|
|
24
|
+
rescue Timeout::Error
|
|
25
|
+
raise TimeoutError, "UniSender request timed out", cause: nil
|
|
26
|
+
rescue OpenSSL::SSL::SSLError
|
|
27
|
+
raise TransportError, "UniSender TLS connection failed", cause: nil
|
|
28
|
+
rescue IOError, SystemCallError, SocketError
|
|
29
|
+
raise TransportError.new("UniSender connection failed", retryable: true), cause: nil
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
private
|
|
33
|
+
|
|
34
|
+
def exchange(uri, headers:, body:)
|
|
35
|
+
request = Net::HTTP::Post.new(uri, headers)
|
|
36
|
+
request.body = body
|
|
37
|
+
response = configured_http(uri).request(request)
|
|
38
|
+
headers = response.to_hash.transform_values { |items| items.join(", ") }
|
|
39
|
+
HTTPResponse.new(status: response.code.to_i, headers:, body: response.body.to_s)
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
def configured_http(uri)
|
|
43
|
+
http = @http_factory.call(uri)
|
|
44
|
+
http.use_ssl = true
|
|
45
|
+
http.verify_mode = OpenSSL::SSL::VERIFY_PEER
|
|
46
|
+
http.open_timeout = @open_timeout
|
|
47
|
+
http.read_timeout = @read_timeout
|
|
48
|
+
http.write_timeout = @write_timeout
|
|
49
|
+
http.max_retries = 0
|
|
50
|
+
http
|
|
51
|
+
end
|
|
52
|
+
end
|
|
53
|
+
end
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Unisender
|
|
4
|
+
# Structural checks only. Account permissions and business rules belong to the API.
|
|
5
|
+
module Validation
|
|
6
|
+
def self.operation!(method, params)
|
|
7
|
+
case method
|
|
8
|
+
when "importContacts" then import!(params)
|
|
9
|
+
when "getTemplate" then template!(params)
|
|
10
|
+
when "createEmailMessage" then email_message!(params)
|
|
11
|
+
when "createCampaign" then campaign!(params)
|
|
12
|
+
end
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
def self.email_message!(params)
|
|
16
|
+
required!(params, %w[subject body]) if params[:template_id].nil? && params[:system_template_id].nil?
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
def self.campaign!(params)
|
|
20
|
+
return unless params[:contacts] && params[:contacts_url]
|
|
21
|
+
|
|
22
|
+
raise ArgumentError,
|
|
23
|
+
"contacts and contacts_url are mutually exclusive"
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
def self.template!(params)
|
|
27
|
+
return unless params[:template_id].nil? && params[:system_template_id].nil?
|
|
28
|
+
|
|
29
|
+
raise ArgumentError, "Provide template_id or system_template_id"
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
def self.import!(params)
|
|
33
|
+
fields = params[:field_names]
|
|
34
|
+
unless fields.is_a?(Array) && fields.all?(String) && fields.include?("email") && fields.uniq == fields
|
|
35
|
+
raise ArgumentError, "field_names must contain unique column names including email"
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
rows!(params[:data], fields.size)
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
def self.rows!(rows, width)
|
|
42
|
+
unless rows.is_a?(Array) && (1..500).cover?(rows.size) && rows.all? do |row|
|
|
43
|
+
row.is_a?(Array) && row.size == width
|
|
44
|
+
end
|
|
45
|
+
raise ArgumentError, "data must contain 1 to 500 rows matching field_names"
|
|
46
|
+
end
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
def self.required!(params, names)
|
|
50
|
+
names.each do |name|
|
|
51
|
+
value = params[name.to_sym]
|
|
52
|
+
raise ArgumentError, "Missing required parameter: #{name}" if missing?(value)
|
|
53
|
+
end
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
def self.missing?(value)
|
|
57
|
+
case value
|
|
58
|
+
when nil then true
|
|
59
|
+
when String, Array, Hash then value.empty?
|
|
60
|
+
else false
|
|
61
|
+
end
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
def self.comma_list(value)
|
|
65
|
+
unless value.all? { |item| item.is_a?(String) || item.is_a?(Integer) }
|
|
66
|
+
raise ArgumentError, "Comma-separated lists require strings or integers"
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
value.join(",")
|
|
70
|
+
end
|
|
71
|
+
end
|
|
72
|
+
end
|
data/lib/unisender.rb
ADDED
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "unisender/version"
|
|
4
|
+
require "uri"
|
|
5
|
+
require_relative "unisender/response"
|
|
6
|
+
require_relative "unisender/errors"
|
|
7
|
+
require_relative "unisender/protocol"
|
|
8
|
+
require_relative "unisender/form"
|
|
9
|
+
require_relative "unisender/transport"
|
|
10
|
+
require_relative "unisender/validation"
|
|
11
|
+
require_relative "unisender/event_payload"
|
|
12
|
+
require_relative "unisender/retry_policy"
|
|
13
|
+
require_relative "unisender/client"
|
|
14
|
+
require_relative "unisender/events"
|
|
15
|
+
require_relative "unisender/paginated_operations"
|
|
16
|
+
require_relative "unisender/pagination"
|
|
17
|
+
require_relative "unisender/batches"
|
|
18
|
+
require_relative "unisender/task_waiter"
|
|
19
|
+
require_relative "unisender/helpers"
|
|
20
|
+
require_relative "unisender/operations/contacts_1"
|
|
21
|
+
require_relative "unisender/operations/contacts_2"
|
|
22
|
+
require_relative "unisender/operations/inputs_1"
|
|
23
|
+
require_relative "unisender/operations/messages_1"
|
|
24
|
+
require_relative "unisender/operations/messages_2"
|
|
25
|
+
require_relative "unisender/operations/messages_3"
|
|
26
|
+
require_relative "unisender/operations/notes_1"
|
|
27
|
+
require_relative "unisender/operations/statistics_1"
|
|
28
|
+
require_relative "unisender/operations/statistics_2"
|
|
29
|
+
require_relative "unisender/operations/templates_1"
|
data/sig/operations.rbs
ADDED
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
module Unisender
|
|
2
|
+
type id = String | Integer
|
|
3
|
+
type flag = bool | 0 | 1
|
|
4
|
+
type list = { "id" => id, "title" => String }
|
|
5
|
+
type list_created = { "id" => id }
|
|
6
|
+
type subscription = { "person_id" => id }
|
|
7
|
+
type message_created = { "message_id" => id }
|
|
8
|
+
type campaign_created = { "campaign_id" => id, "status" => String }
|
|
9
|
+
type task_created = { "task_uuid" => String }
|
|
10
|
+
type task_completed = { "status" => String, "file_to_download" => String }
|
|
11
|
+
|
|
12
|
+
type object = Hash[String, json]
|
|
13
|
+
type objects = Array[object]
|
|
14
|
+
type contact_count = { "count" => String }
|
|
15
|
+
type total_contacts = { "total" => Integer }
|
|
16
|
+
type delivery_status = { "status" => String }
|
|
17
|
+
type message_version = { "message_id" => id, "actual_version_id" => id }
|
|
18
|
+
type note = { "id" => id, "content" => String }
|
|
19
|
+
type event_accepted = { "success" => true }
|
|
20
|
+
|
|
21
|
+
class Client
|
|
22
|
+
def create_list: (title: String, ?before_subscribe_url: String | nil, ?after_subscribe_url: String | nil, ?sub_user_rights: params | nil) -> Response[list_created]
|
|
23
|
+
def delete_list: (list_id: id) -> Response[object | objects]
|
|
24
|
+
def exclude: (contact_type: String, contact: String, ?list_ids: String | Array[id] | nil) -> Response[object | objects]
|
|
25
|
+
def export_contacts: (?notify_url: String | nil, ?list_id: id | nil, ?field_names: Array[String] | nil, ?email: String | nil, ?phone: String | nil, ?tag: String | nil, ?email_status: String | nil, ?phone_status: String | nil, ?contact_status: String | nil) -> Response[task_created]
|
|
26
|
+
def get_contact_count: (list_id: id, params: params) -> Response[contact_count]
|
|
27
|
+
def get_lists: () -> Response[Array[list]]
|
|
28
|
+
def get_total_contacts_count: (login: String) -> Response[total_contacts]
|
|
29
|
+
def import_contacts: (field_names: Array[String], data: Array[Array[parameter]], ?overwrite_tags: flag | nil, ?overwrite_lists: flag | nil, ?telegram_bot_channel_id: String | nil) -> Response[object]
|
|
30
|
+
def subscribe: (list_ids: String | Array[id], fields: params, ?tags: String | Array[id] | nil, ?double_optin: Integer | nil, ?overwrite: Integer | nil) -> Response[subscription]
|
|
31
|
+
def update_list: (list_id: id, title: String, ?before_subscribe_url: String | nil, ?after_subscribe_url: String | nil, ?sub_user_rights: params | nil) -> Response[object | objects]
|
|
32
|
+
def is_contact_in_lists: (email: String, list_ids: String | Array[id], condition: String) -> Response[bool]
|
|
33
|
+
def get_contact: (email: String, ?include_lists: flag | nil, ?include_fields: flag | nil, ?include_details: flag | nil) -> Response[object]
|
|
34
|
+
def unsubscribe: (contact_type: String, contact: String, ?list_ids: String | Array[id] | nil) -> Response[object | objects]
|
|
35
|
+
def cancel_campaign: (campaign_id: id) -> Response[objects]
|
|
36
|
+
def check_email: (email_id: String | Array[id]) -> Response[objects]
|
|
37
|
+
def check_sms: (sms_id: id) -> Response[delivery_status]
|
|
38
|
+
def create_campaign: (message_id: id, ?start_time: String | nil, ?timezone: String | nil, ?track_read: flag | nil, ?track_links: flag | nil, ?contacts: String | Array[id] | nil, ?contacts_url: String | nil, ?track_ga: flag | nil, ?payment_limit: Integer | Float | nil, ?payment_currency: String | nil, ?ga_medium: String | nil, ?ga_source: String | nil, ?ga_campaign: String | nil, ?ga_content: String | nil, ?ga_term: String | nil) -> Response[campaign_created]
|
|
39
|
+
def create_email_message: (sender_name: String, sender_email: String, list_id: id, ?subject: String | nil, ?body: String | nil, ?text_body: String | nil, ?generate_text: flag | nil, ?tag: String | nil, ?attachments: Hash[String, String] | nil, ?lang: String | nil, ?template_id: id | nil, ?system_template_id: id | nil, ?wrap_type: String | nil, ?categories: String | Array[id] | nil) -> Response[message_created]
|
|
40
|
+
def create_sms_message: (sender: String, body: String, list_id: id, ?tag: String | nil) -> Response[message_created]
|
|
41
|
+
def delete_message: (message_id: id) -> Response[object | objects]
|
|
42
|
+
def get_actual_message_version: (message_id: id) -> Response[message_version]
|
|
43
|
+
def get_web_version: (campaign_id: id) -> Response[object]
|
|
44
|
+
def send_email: (email: String, sender_name: String, sender_email: String, subject: String, body: String, list_id: id, ?attachments: Hash[String, String] | nil, ?lang: String | nil, ?track_read: flag | nil, ?track_links: flag | nil, ?cc: String | nil, ?headers: String | params | nil, ?images_as: String | nil, ?ref_key: String | nil, ?error_checking: flag | nil, ?metadata: params | nil) -> Response[object | objects]
|
|
45
|
+
def send_sms: (phone: String, sender: String, text: String) -> Response[object]
|
|
46
|
+
def send_test_email: (email: String | Array[id], id: id) -> Response[object | objects]
|
|
47
|
+
def update_email_message: (id: id, ?sender_name: String | nil, ?sender_email: String | nil, ?subject: String | nil, ?body: String | nil, ?list_id: id | nil, ?text_body: String | nil, ?lang: String | nil, ?categories: String | Array[id] | nil) -> Response[message_created]
|
|
48
|
+
def update_opt_in_email: (sender_name: String, sender_email: String, subject: String, body: String, list_id: id) -> Response[object | objects]
|
|
49
|
+
def get_sender_domain_list: (username: String, ?domain: String | nil, ?limit: Integer | nil, ?offset: Integer | nil) -> Response[objects]
|
|
50
|
+
def get_campaign_common_stats: (campaign_id: id) -> Response[object]
|
|
51
|
+
def get_campaign_delivery_stats: (campaign_id: id, ?notify_url: String | nil, ?changed_since: String | nil, ?field_ids: Array[id] | nil) -> Response[task_created]
|
|
52
|
+
def get_campaign_status: (campaign_id: id) -> Response[delivery_status]
|
|
53
|
+
def get_messages: (date_from: String, date_to: String, ?limit: Integer | nil, ?offset: Integer | nil) -> Response[objects]
|
|
54
|
+
def get_visited_links: (campaign_id: id, ?group: flag | nil) -> Response[object]
|
|
55
|
+
def list_messages: (date_from: String, date_to: String, ?limit: Integer | nil, ?offset: Integer | nil) -> Response[objects]
|
|
56
|
+
def get_campaigns: (from: String, ?to: String | nil, ?limit: Integer | nil, ?offset: Integer | nil) -> Response[objects]
|
|
57
|
+
def get_message: (id: id) -> Response[objects]
|
|
58
|
+
def get_task_result: (task_uuid: String) -> Response[object]
|
|
59
|
+
def create_email_template: (title: String, subject: String, body: String, ?description: String | nil, ?text_body: String | nil, ?lang: String | nil) -> Response[list_created]
|
|
60
|
+
def delete_template: (template_id: id) -> Response[object | objects]
|
|
61
|
+
def get_template: (?template_id: id | nil, ?system_template_id: id | nil) -> Response[object]
|
|
62
|
+
def get_templates: (?type: String | nil, ?date_from: String | nil, ?date_to: String | nil, ?limit: Integer | nil, ?offset: Integer | nil) -> Response[objects]
|
|
63
|
+
def list_templates: (?type: String | nil, ?date_from: String | nil, ?date_to: String | nil, ?limit: Integer | nil, ?offset: Integer | nil) -> Response[objects]
|
|
64
|
+
def update_email_template: (template_id: id, ?title: String | nil, ?description: String | nil, ?subject: String | nil, ?body: String | nil, ?text_body: String | nil, ?lang: String | nil) -> Response[list_created]
|
|
65
|
+
def create_field: (name: String, type: String, ?public_name: String | nil) -> Response[list_created]
|
|
66
|
+
def delete_field: (id: id) -> Response[object | objects]
|
|
67
|
+
def delete_tag: (id: id) -> Response[object | objects]
|
|
68
|
+
def get_fields: () -> Response[objects]
|
|
69
|
+
def get_tags: () -> Response[objects]
|
|
70
|
+
def update_field: (id: id, name: String, ?public_name: String | nil) -> Response[object | objects]
|
|
71
|
+
def get_contact_field_values: (email: String, field_ids: String | Array[id]) -> Response[object]
|
|
72
|
+
def create_subscriber_note: (subscriber_id: id, content: String) -> Response[list_created]
|
|
73
|
+
def update_subscriber_note: (id: id, content: String) -> Response[list_created]
|
|
74
|
+
def delete_subscriber_note: (id: id) -> Response[object | objects]
|
|
75
|
+
def get_subscriber_note: (id: id) -> Response[note]
|
|
76
|
+
def get_subscriber_notes: (subscriber_id: id, ?limit: Integer | nil, ?offset: Integer | nil, ?order_type: String | nil, ?order_by: String | nil, ?is_pinned: flag | nil) -> Response[objects]
|
|
77
|
+
end
|
|
78
|
+
end
|
data/sig/unisender.rbs
ADDED
|
@@ -0,0 +1,218 @@
|
|
|
1
|
+
module Unisender
|
|
2
|
+
VERSION: String
|
|
3
|
+
|
|
4
|
+
type json = nil | bool | Integer | Float | String | Array[json] | Hash[String, json]
|
|
5
|
+
type parameter = nil | bool | Integer | Float | String | Array[parameter] | Hash[String | Symbol | Integer, parameter]
|
|
6
|
+
type params = Hash[String | Symbol | Integer, parameter]
|
|
7
|
+
type duration = Integer | Float
|
|
8
|
+
|
|
9
|
+
class HTTPResponse < Data
|
|
10
|
+
attr_reader status: Integer
|
|
11
|
+
attr_reader headers: Hash[String, String]
|
|
12
|
+
attr_reader body: String
|
|
13
|
+
def self.new: (status: Integer, headers: Hash[String, String], body: String) -> HTTPResponse
|
|
14
|
+
def inspect: () -> String
|
|
15
|
+
alias to_s inspect
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
class Response[out Result = json]
|
|
19
|
+
EMPTY_WARNINGS: Array[json]
|
|
20
|
+
@data: Hash[String, json]
|
|
21
|
+
@status: Integer
|
|
22
|
+
@headers: Hash[String, String]
|
|
23
|
+
attr_reader data: Hash[String, json]
|
|
24
|
+
attr_reader status: Integer
|
|
25
|
+
attr_reader headers: Hash[String, String]
|
|
26
|
+
def initialize: (data: Hash[String, json], status: Integer, headers: Hash[String, String]) -> void
|
|
27
|
+
def result: () -> Result
|
|
28
|
+
def warnings: () -> json
|
|
29
|
+
def inspect: () -> String
|
|
30
|
+
private
|
|
31
|
+
def immutable: (json) -> json
|
|
32
|
+
def immutable_hash: (Hash[String, json]) -> Hash[String, json]
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
class Error < StandardError
|
|
36
|
+
OPERATIONS: Array[String]
|
|
37
|
+
@operation: String?
|
|
38
|
+
attr_reader operation: String?
|
|
39
|
+
def with_operation: (String) -> self
|
|
40
|
+
def to_s: () -> String
|
|
41
|
+
def inspect: () -> String
|
|
42
|
+
end
|
|
43
|
+
class APIError < Error
|
|
44
|
+
SAFE_CODES: Array[String]
|
|
45
|
+
attr_reader response: Response
|
|
46
|
+
attr_reader code: json
|
|
47
|
+
attr_reader details: json
|
|
48
|
+
def initialize: (Response) -> void
|
|
49
|
+
end
|
|
50
|
+
class HTTPError < Error
|
|
51
|
+
attr_reader response: Response
|
|
52
|
+
def initialize: (Response) -> void
|
|
53
|
+
end
|
|
54
|
+
class InvalidResponseError < Error
|
|
55
|
+
end
|
|
56
|
+
class TransportError < Error
|
|
57
|
+
@retryable: bool
|
|
58
|
+
def initialize: (String, ?retryable: bool) -> void
|
|
59
|
+
def retryable?: () -> bool
|
|
60
|
+
end
|
|
61
|
+
class TimeoutError < TransportError
|
|
62
|
+
end
|
|
63
|
+
class EventError < HTTPError
|
|
64
|
+
def code: () -> json
|
|
65
|
+
def details: () -> json
|
|
66
|
+
def possibly_partially_accepted?: () -> bool
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
class BatchError < Error
|
|
70
|
+
attr_reader completed_responses: Array[Response]
|
|
71
|
+
attr_reader failed_range: Range[Integer]
|
|
72
|
+
attr_reader error: StandardError
|
|
73
|
+
def initialize: (completed_responses: Array[Response], failed_range: Range[Integer], error: StandardError) -> void
|
|
74
|
+
end
|
|
75
|
+
class PaginationError < Error
|
|
76
|
+
end
|
|
77
|
+
class PartialFailure < Error
|
|
78
|
+
attr_reader response: Response
|
|
79
|
+
def initialize: (Response) -> void
|
|
80
|
+
end
|
|
81
|
+
class TaskError < Error
|
|
82
|
+
attr_reader response: Response
|
|
83
|
+
def initialize: (Response) -> void
|
|
84
|
+
end
|
|
85
|
+
|
|
86
|
+
module PaginatedOperations
|
|
87
|
+
DEFINITIONS: Hash[String, [String, Array[Symbol], Array[Symbol], Integer?]]
|
|
88
|
+
def self.call: (Client, String | Symbol, params) -> Response[objects]
|
|
89
|
+
def self.page_limit: (String | Symbol) -> Integer?
|
|
90
|
+
def self.definition: (String | Symbol) -> [String, Array[Symbol], Array[Symbol], Integer?]
|
|
91
|
+
end
|
|
92
|
+
|
|
93
|
+
class Pagination
|
|
94
|
+
@client: Client
|
|
95
|
+
@method: String
|
|
96
|
+
@page_size: Integer
|
|
97
|
+
@max_pages: Integer
|
|
98
|
+
@params: params
|
|
99
|
+
def initialize: (Client, String | Symbol, Integer, Integer, params) -> void
|
|
100
|
+
def each: () -> Enumerator[Response, void]
|
|
101
|
+
private
|
|
102
|
+
def validate!: (Integer?) -> void
|
|
103
|
+
def validate_limit!: (Integer?) -> void
|
|
104
|
+
def page_length: (Response) -> Integer
|
|
105
|
+
def initial_offset: () -> Integer
|
|
106
|
+
def fetch_pages: (_ResponseSink, Integer) -> void
|
|
107
|
+
end
|
|
108
|
+
|
|
109
|
+
module Batches
|
|
110
|
+
def self.each: [T] (Enumerable[T], Integer, Integer) { (Array[T]) -> Response } -> Enumerator[Response, void]
|
|
111
|
+
def self.execute: [T] (Array[T], Integer, Array[Response]) { (Array[T]) -> Response } -> Response
|
|
112
|
+
end
|
|
113
|
+
|
|
114
|
+
class TaskWaiter
|
|
115
|
+
PENDING: Array[String]
|
|
116
|
+
@client: Client
|
|
117
|
+
@clock: ^() -> Float
|
|
118
|
+
@sleeper: ^(duration) -> void
|
|
119
|
+
def initialize: (Client, clock: ^() -> Float, sleeper: ^(duration) -> void) -> void
|
|
120
|
+
def call: (String, duration, duration) -> Response
|
|
121
|
+
private
|
|
122
|
+
def validate!: (duration, duration) -> void
|
|
123
|
+
def complete?: (Response) -> bool
|
|
124
|
+
def wait: (duration, Float) -> void
|
|
125
|
+
end
|
|
126
|
+
|
|
127
|
+
module EventPayload
|
|
128
|
+
def self.encode: (Array[params]) -> String
|
|
129
|
+
def self.validate: (json) -> Hash[String, json]
|
|
130
|
+
def self.validate_name: (json) -> void
|
|
131
|
+
def self.validate_email: (json) -> void
|
|
132
|
+
def self.normalize: (parameter, Integer) -> json
|
|
133
|
+
def self.normalize_hash: (params, Integer) -> Hash[String, json]
|
|
134
|
+
end
|
|
135
|
+
|
|
136
|
+
class RetryPolicy
|
|
137
|
+
READ_ONLY: Array[String]
|
|
138
|
+
TRANSIENT_CODES: Array[String]
|
|
139
|
+
@max_retries: Integer
|
|
140
|
+
@max_elapsed: duration
|
|
141
|
+
@clock: ^() -> Float
|
|
142
|
+
@sleeper: ^(Float) -> void
|
|
143
|
+
@random: ^() -> Float
|
|
144
|
+
def initialize: (?max_retries: Integer, ?max_elapsed: duration, ?clock: ( ^() -> Float)?, ?sleeper: ( ^(Float) -> void)?, ?random: ( ^() -> Float)?) -> void
|
|
145
|
+
def call: [T] (String) { () -> T } -> T
|
|
146
|
+
private
|
|
147
|
+
def transient?: (Error) -> bool
|
|
148
|
+
def retry_after: (Error) -> Float
|
|
149
|
+
def validate!: (Integer, duration) -> void
|
|
150
|
+
def wait?: (Error, Integer, Float) -> bool
|
|
151
|
+
end
|
|
152
|
+
|
|
153
|
+
module Protocol
|
|
154
|
+
def self.parse: (HTTPResponse, ?method: String?) -> Response
|
|
155
|
+
def self.result?: (Hash[String, json], String?) -> bool
|
|
156
|
+
def self.decode: (HTTPResponse) -> Hash[String, json]
|
|
157
|
+
end
|
|
158
|
+
|
|
159
|
+
module Form
|
|
160
|
+
def self.encode: (params) -> String
|
|
161
|
+
def self.pairs: (parameter, String?, Integer) -> Array[[String, String]]
|
|
162
|
+
def self.hash_pairs: (params, String?, Integer) -> Array[[String, String]]
|
|
163
|
+
def self.scalar: (parameter, String) -> String
|
|
164
|
+
def self.numeric: (parameter) -> String
|
|
165
|
+
def self.text: (String) -> String
|
|
166
|
+
end
|
|
167
|
+
|
|
168
|
+
module Validation
|
|
169
|
+
def self.operation!: (String, params) -> void
|
|
170
|
+
def self.template!: (params) -> void
|
|
171
|
+
def self.import!: (params) -> void
|
|
172
|
+
def self.email_message!: (params) -> void
|
|
173
|
+
def self.campaign!: (params) -> void
|
|
174
|
+
def self.rows!: (parameter, Integer) -> void
|
|
175
|
+
def self.required!: (params, Array[String]) -> void
|
|
176
|
+
def self.missing?: (parameter) -> bool
|
|
177
|
+
def self.comma_list: (Array[parameter]) -> String
|
|
178
|
+
end
|
|
179
|
+
|
|
180
|
+
interface _Transport
|
|
181
|
+
def post: (URI::Generic, headers: Hash[String, String], body: String) -> HTTPResponse
|
|
182
|
+
end
|
|
183
|
+
|
|
184
|
+
interface _ResponseSink
|
|
185
|
+
def <<: (Response) -> void
|
|
186
|
+
end
|
|
187
|
+
|
|
188
|
+
class Transport
|
|
189
|
+
@open_timeout: duration
|
|
190
|
+
@read_timeout: duration
|
|
191
|
+
@write_timeout: duration
|
|
192
|
+
@http_factory: ^(URI::Generic) -> Net::HTTP
|
|
193
|
+
def initialize: (?open_timeout: duration, ?read_timeout: duration, ?write_timeout: duration, ?http_factory: (^(URI::Generic) -> Net::HTTP)?) -> void
|
|
194
|
+
def post: (URI::Generic, headers: Hash[String, String], body: String) -> HTTPResponse
|
|
195
|
+
private
|
|
196
|
+
def configured_http: (URI::Generic) -> Net::HTTP
|
|
197
|
+
def exchange: (URI::Generic, headers: Hash[String, String], body: String) -> HTTPResponse
|
|
198
|
+
end
|
|
199
|
+
|
|
200
|
+
class Client
|
|
201
|
+
RESERVED_PARAMETERS: Array[String]
|
|
202
|
+
@api_key: String
|
|
203
|
+
@language: String
|
|
204
|
+
@transport: _Transport
|
|
205
|
+
@retry_policy: RetryPolicy
|
|
206
|
+
def initialize: (api_key: String, ?transport: _Transport, ?language: String | Symbol, ?retry_policy: RetryPolicy) -> void
|
|
207
|
+
def request: (String, ?params: params) -> Response
|
|
208
|
+
def track_events: (events: Array[params]) -> Response[event_accepted]
|
|
209
|
+
def each_page: (String | Symbol, ?page_size: Integer, ?max_pages: Integer, **parameter) -> Enumerator[Response, void]
|
|
210
|
+
def import_contacts_in_batches: (field_names: Array[String], data: Enumerable[Array[parameter]], ?batch_size: Integer, ?overwrite_tags: flag?, ?overwrite_lists: flag?, ?telegram_bot_channel_id: String?) -> Enumerator[Response, void]
|
|
211
|
+
def track_events_in_batches: (events: Enumerable[params], ?batch_size: Integer) -> Enumerator[Response, void]
|
|
212
|
+
def wait_for_task: (task_uuid: String, ?interval: duration, ?timeout: duration, ?clock: ( ^() -> Float)?, ?sleeper: ( ^(duration) -> void)?) -> Response[task_completed]
|
|
213
|
+
def inspect: () -> String
|
|
214
|
+
private
|
|
215
|
+
def invoke: [Result] (String, params, ?required: Array[String], ?comma: Array[String]) -> Response[Result]
|
|
216
|
+
def validate_request: (String, params) -> void
|
|
217
|
+
end
|
|
218
|
+
end
|
metadata
ADDED
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
|
2
|
+
name: unisender-ruby
|
|
3
|
+
version: !ruby/object:Gem::Version
|
|
4
|
+
version: 0.1.0
|
|
5
|
+
platform: ruby
|
|
6
|
+
authors:
|
|
7
|
+
- Leonid Svyatov
|
|
8
|
+
bindir: bin
|
|
9
|
+
cert_chain: []
|
|
10
|
+
date: 1980-01-02 00:00:00.000000000 Z
|
|
11
|
+
dependencies: []
|
|
12
|
+
description: A Ruby library for integrating with the UniSender API.
|
|
13
|
+
email:
|
|
14
|
+
- leonid@svyatov.com
|
|
15
|
+
executables: []
|
|
16
|
+
extensions: []
|
|
17
|
+
extra_rdoc_files: []
|
|
18
|
+
files:
|
|
19
|
+
- CHANGELOG.md
|
|
20
|
+
- CONTRIBUTING.md
|
|
21
|
+
- LICENSE.txt
|
|
22
|
+
- README.md
|
|
23
|
+
- docs/api.md
|
|
24
|
+
- examples/campaign.rb
|
|
25
|
+
- examples/check_connection.rb
|
|
26
|
+
- examples/events.rb
|
|
27
|
+
- examples/export.rb
|
|
28
|
+
- examples/import.rb
|
|
29
|
+
- examples/offline.rb
|
|
30
|
+
- examples/quickstart.rb
|
|
31
|
+
- examples/subscribe.rb
|
|
32
|
+
- lib/unisender.rb
|
|
33
|
+
- lib/unisender/batches.rb
|
|
34
|
+
- lib/unisender/client.rb
|
|
35
|
+
- lib/unisender/errors.rb
|
|
36
|
+
- lib/unisender/event_payload.rb
|
|
37
|
+
- lib/unisender/events.rb
|
|
38
|
+
- lib/unisender/form.rb
|
|
39
|
+
- lib/unisender/helpers.rb
|
|
40
|
+
- lib/unisender/operations/contacts_1.rb
|
|
41
|
+
- lib/unisender/operations/contacts_2.rb
|
|
42
|
+
- lib/unisender/operations/inputs_1.rb
|
|
43
|
+
- lib/unisender/operations/messages_1.rb
|
|
44
|
+
- lib/unisender/operations/messages_2.rb
|
|
45
|
+
- lib/unisender/operations/messages_3.rb
|
|
46
|
+
- lib/unisender/operations/notes_1.rb
|
|
47
|
+
- lib/unisender/operations/statistics_1.rb
|
|
48
|
+
- lib/unisender/operations/statistics_2.rb
|
|
49
|
+
- lib/unisender/operations/templates_1.rb
|
|
50
|
+
- lib/unisender/paginated_operations.rb
|
|
51
|
+
- lib/unisender/pagination.rb
|
|
52
|
+
- lib/unisender/protocol.rb
|
|
53
|
+
- lib/unisender/response.rb
|
|
54
|
+
- lib/unisender/retry_policy.rb
|
|
55
|
+
- lib/unisender/task_waiter.rb
|
|
56
|
+
- lib/unisender/transport.rb
|
|
57
|
+
- lib/unisender/validation.rb
|
|
58
|
+
- lib/unisender/version.rb
|
|
59
|
+
- sig/operations.rbs
|
|
60
|
+
- sig/unisender.rbs
|
|
61
|
+
homepage: https://github.com/svyatov/unisender-ruby
|
|
62
|
+
licenses:
|
|
63
|
+
- MIT
|
|
64
|
+
metadata:
|
|
65
|
+
rubygems_mfa_required: 'true'
|
|
66
|
+
source_code_uri: https://github.com/svyatov/unisender-ruby
|
|
67
|
+
bug_tracker_uri: https://github.com/svyatov/unisender-ruby/issues
|
|
68
|
+
documentation_uri: https://github.com/svyatov/unisender-ruby/blob/main/README.md
|
|
69
|
+
changelog_uri: https://github.com/svyatov/unisender-ruby/blob/main/CHANGELOG.md
|
|
70
|
+
rdoc_options: []
|
|
71
|
+
require_paths:
|
|
72
|
+
- lib
|
|
73
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
|
74
|
+
requirements:
|
|
75
|
+
- - ">="
|
|
76
|
+
- !ruby/object:Gem::Version
|
|
77
|
+
version: '3.3'
|
|
78
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
|
79
|
+
requirements:
|
|
80
|
+
- - ">="
|
|
81
|
+
- !ruby/object:Gem::Version
|
|
82
|
+
version: '0'
|
|
83
|
+
requirements: []
|
|
84
|
+
rubygems_version: 4.0.23
|
|
85
|
+
specification_version: 4
|
|
86
|
+
summary: Ruby client for the UniSender API.
|
|
87
|
+
test_files: []
|