warb 0.1.0 → 0.1.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/.rubocop.yml +12 -5
- data/README.md +8 -0
- data/Rakefile +3 -3
- data/docs/messages/interactive_call_to_action_url.md +9 -9
- data/docs/messages/interactive_list.md +2 -2
- data/docs/messages/interactive_reply_button.md +9 -9
- data/docs/messages/template.md +13 -13
- data/examples/audio.rb +10 -10
- data/examples/document.rb +34 -34
- data/examples/image.rb +22 -22
- data/examples/interactive_call_to_action_url.rb +46 -46
- data/examples/interactive_list.rb +61 -61
- data/examples/interactive_reply_button.rb +43 -43
- data/examples/location.rb +32 -32
- data/examples/location_request.rb +11 -11
- data/examples/message.rb +8 -8
- data/examples/sticker.rb +10 -10
- data/examples/video.rb +22 -22
- data/examples/webhook.rb +44 -42
- data/lib/warb/client.rb +7 -5
- data/lib/warb/components/action.rb +12 -8
- data/lib/warb/components/button.rb +2 -2
- data/lib/warb/components/component.rb +1 -1
- data/lib/warb/components/copy_code_button.rb +4 -4
- data/lib/warb/components/quick_reply_button.rb +1 -1
- data/lib/warb/components/url_button.rb +4 -4
- data/lib/warb/components/voice_call_button.rb +1 -1
- data/lib/warb/configuration.rb +4 -1
- data/lib/warb/connection.rb +15 -9
- data/lib/warb/dispatcher.rb +3 -2
- data/lib/warb/dispatcher_concern.rb +2 -0
- data/lib/warb/errors.rb +27 -0
- data/lib/warb/indicator_dispatcher.rb +4 -4
- data/lib/warb/language.rb +2 -2
- data/lib/warb/media_dispatcher.rb +10 -10
- data/lib/warb/resources/audio.rb +1 -1
- data/lib/warb/resources/contact.rb +22 -20
- data/lib/warb/resources/currency.rb +6 -4
- data/lib/warb/resources/date_time.rb +1 -1
- data/lib/warb/resources/document.rb +1 -1
- data/lib/warb/resources/flow.rb +10 -8
- data/lib/warb/resources/image.rb +1 -1
- data/lib/warb/resources/interactive_call_to_action_url.rb +10 -8
- data/lib/warb/resources/interactive_list.rb +7 -5
- data/lib/warb/resources/interactive_reply_button.rb +10 -8
- data/lib/warb/resources/location.rb +1 -1
- data/lib/warb/resources/location_request.rb +5 -3
- data/lib/warb/resources/reaction.rb +1 -1
- data/lib/warb/resources/resource.rb +4 -4
- data/lib/warb/resources/sticker.rb +1 -1
- data/lib/warb/resources/template.rb +30 -29
- data/lib/warb/resources/text.rb +4 -4
- data/lib/warb/resources/video.rb +1 -1
- data/lib/warb/response.rb +33 -0
- data/lib/warb/response_error_handler.rb +42 -0
- data/lib/warb/template_dispatcher.rb +4 -2
- data/lib/warb/utils.rb +3 -1
- data/lib/warb/version.rb +1 -1
- data/lib/warb.rb +50 -42
- metadata +10 -3
|
@@ -27,19 +27,19 @@ module Warb
|
|
|
27
27
|
raise NotImplementedError
|
|
28
28
|
end
|
|
29
29
|
|
|
30
|
-
def
|
|
30
|
+
def add_text_header
|
|
31
31
|
raise NotImplementedError
|
|
32
32
|
end
|
|
33
33
|
|
|
34
|
-
def
|
|
34
|
+
def add_image_header
|
|
35
35
|
raise NotImplementedError
|
|
36
36
|
end
|
|
37
37
|
|
|
38
|
-
def
|
|
38
|
+
def add_video_header
|
|
39
39
|
raise NotImplementedError
|
|
40
40
|
end
|
|
41
41
|
|
|
42
|
-
def
|
|
42
|
+
def add_document_header
|
|
43
43
|
raise NotImplementedError
|
|
44
44
|
end
|
|
45
45
|
|
|
@@ -6,7 +6,7 @@ module Warb
|
|
|
6
6
|
attr_accessor :name, :language, :resources, :header, :buttons
|
|
7
7
|
|
|
8
8
|
def initialize(**params)
|
|
9
|
-
super
|
|
9
|
+
super
|
|
10
10
|
|
|
11
11
|
@name = params[:name]
|
|
12
12
|
@language = params[:language]
|
|
@@ -14,9 +14,10 @@ module Warb
|
|
|
14
14
|
@buttons = []
|
|
15
15
|
end
|
|
16
16
|
|
|
17
|
+
# rubocop:disable Metrics/MethodLength
|
|
17
18
|
def build_payload
|
|
18
19
|
{
|
|
19
|
-
type:
|
|
20
|
+
type: 'template',
|
|
20
21
|
template: {
|
|
21
22
|
name: name,
|
|
22
23
|
language: {
|
|
@@ -30,37 +31,38 @@ module Warb
|
|
|
30
31
|
}
|
|
31
32
|
}
|
|
32
33
|
end
|
|
34
|
+
# rubocop:enable Metrics/MethodLength
|
|
33
35
|
|
|
34
|
-
def add_currency_parameter(parameter_name = nil, **params, &
|
|
35
|
-
add_parameter(parameter_name, Currency.new(**params), &
|
|
36
|
+
def add_currency_parameter(parameter_name = nil, **params, &)
|
|
37
|
+
add_parameter(parameter_name, Currency.new(**params), &)
|
|
36
38
|
end
|
|
37
39
|
|
|
38
|
-
def add_date_time_parameter(parameter_name = nil, **params, &
|
|
39
|
-
add_parameter(parameter_name, DateTime.new(**params), &
|
|
40
|
+
def add_date_time_parameter(parameter_name = nil, **params, &)
|
|
41
|
+
add_parameter(parameter_name, DateTime.new(**params), &)
|
|
40
42
|
end
|
|
41
43
|
|
|
42
|
-
def add_text_parameter(parameter_name = nil, **params, &
|
|
43
|
-
add_parameter(parameter_name, Text.new(**params), &
|
|
44
|
+
def add_text_parameter(parameter_name = nil, **params, &)
|
|
45
|
+
add_parameter(parameter_name, Text.new(**params), &)
|
|
44
46
|
end
|
|
45
47
|
|
|
46
|
-
def
|
|
47
|
-
|
|
48
|
+
def add_text_header(content: nil, message: nil, text: nil, parameter_name: nil, &block)
|
|
49
|
+
add_header(Text.new(content:, message:, text:, parameter_name:), &block)
|
|
48
50
|
end
|
|
49
51
|
|
|
50
|
-
def
|
|
51
|
-
|
|
52
|
+
def add_image_header(media_id: nil, link: nil, &block)
|
|
53
|
+
add_header(Image.new(media_id:, link:), &block)
|
|
52
54
|
end
|
|
53
55
|
|
|
54
|
-
def
|
|
55
|
-
|
|
56
|
+
def add_document_header(media_id: nil, link: nil, filename: nil, &block)
|
|
57
|
+
add_header(Document.new(media_id:, link:, filename:), &block)
|
|
56
58
|
end
|
|
57
59
|
|
|
58
|
-
def
|
|
59
|
-
|
|
60
|
+
def add_video_header(media_id: nil, link: nil, &block)
|
|
61
|
+
add_header(Video.new(media_id:, link:), &block)
|
|
60
62
|
end
|
|
61
63
|
|
|
62
|
-
def
|
|
63
|
-
|
|
64
|
+
def add_location_header(latitude: nil, longitude: nil, address: nil, name: nil, &block)
|
|
65
|
+
add_header(Location.new(latitude:, longitude:, address:, name:), &block)
|
|
64
66
|
end
|
|
65
67
|
|
|
66
68
|
def add_quick_reply_button(index: position, &block)
|
|
@@ -71,7 +73,7 @@ module Warb
|
|
|
71
73
|
add_button(Warb::Components::UrlButton.new(index:, text:), &block)
|
|
72
74
|
end
|
|
73
75
|
|
|
74
|
-
|
|
76
|
+
alias add_auth_code_button add_dynamic_url_button
|
|
75
77
|
|
|
76
78
|
def add_copy_code_button(index: position, coupon_code: nil, &block)
|
|
77
79
|
add_button(Warb::Components::CopyCodeButton.new(index:, coupon_code:), &block)
|
|
@@ -81,26 +83,25 @@ module Warb
|
|
|
81
83
|
add_button(Warb::Components::VoiceCallButton.new(index:), &block)
|
|
82
84
|
end
|
|
83
85
|
|
|
84
|
-
def add_button(instance, &
|
|
86
|
+
def add_button(instance, &)
|
|
85
87
|
return @buttons << instance.to_h unless block_given?
|
|
86
88
|
|
|
87
|
-
@buttons << instance.tap(&
|
|
89
|
+
@buttons << instance.tap(&).to_h
|
|
88
90
|
end
|
|
89
91
|
|
|
90
92
|
private
|
|
91
93
|
|
|
92
|
-
def
|
|
94
|
+
def add_header(instance, &)
|
|
93
95
|
@header = instance
|
|
94
96
|
|
|
95
|
-
block_given? ? @header.tap(&
|
|
97
|
+
block_given? ? @header.tap(&) : @header
|
|
96
98
|
end
|
|
97
99
|
|
|
98
|
-
|
|
99
100
|
def component_header
|
|
100
101
|
return unless header.is_a? Resource
|
|
101
102
|
|
|
102
103
|
{
|
|
103
|
-
type:
|
|
104
|
+
type: 'header',
|
|
104
105
|
parameters: [
|
|
105
106
|
header.build_header
|
|
106
107
|
]
|
|
@@ -111,7 +112,7 @@ module Warb
|
|
|
111
112
|
return if resources.nil? || resources.empty?
|
|
112
113
|
|
|
113
114
|
{
|
|
114
|
-
type:
|
|
115
|
+
type: 'body',
|
|
115
116
|
parameters: build_parameters
|
|
116
117
|
}
|
|
117
118
|
end
|
|
@@ -135,17 +136,17 @@ module Warb
|
|
|
135
136
|
resources.map(&:build_template_positional_parameter)
|
|
136
137
|
end
|
|
137
138
|
|
|
138
|
-
def add_parameter(parameter_name, instance, &
|
|
139
|
+
def add_parameter(parameter_name, instance, &)
|
|
139
140
|
case resources
|
|
140
141
|
when Hash
|
|
141
142
|
resources[parameter_name.to_s] = instance
|
|
142
143
|
when Array
|
|
143
144
|
resources << instance
|
|
144
145
|
else
|
|
145
|
-
initialize_resources(parameter_name, instance, &
|
|
146
|
+
initialize_resources(parameter_name, instance, &)
|
|
146
147
|
end
|
|
147
148
|
|
|
148
|
-
block_given? ? instance.tap(&
|
|
149
|
+
block_given? ? instance.tap(&) : instance
|
|
149
150
|
end
|
|
150
151
|
|
|
151
152
|
def initialize_resources(parameter_name, instance)
|
data/lib/warb/resources/text.rb
CHANGED
|
@@ -6,7 +6,7 @@ module Warb
|
|
|
6
6
|
attr_accessor :content, :text, :message, :preview_url, :parameter_name
|
|
7
7
|
|
|
8
8
|
def build_header
|
|
9
|
-
{ type:
|
|
9
|
+
{ type: 'text', text: message_per_priority }.tap do |header|
|
|
10
10
|
parameter_name ||= @params[:parameter_name]
|
|
11
11
|
header[:parameter_name] = parameter_name unless parameter_name.nil?
|
|
12
12
|
end
|
|
@@ -14,7 +14,7 @@ module Warb
|
|
|
14
14
|
|
|
15
15
|
def build_payload
|
|
16
16
|
{
|
|
17
|
-
type:
|
|
17
|
+
type: 'text',
|
|
18
18
|
text: {
|
|
19
19
|
preview_url: preview_url || @params[:preview_url],
|
|
20
20
|
body: message_per_priority
|
|
@@ -24,7 +24,7 @@ module Warb
|
|
|
24
24
|
|
|
25
25
|
def build_template_named_parameter(parameter_name)
|
|
26
26
|
{
|
|
27
|
-
type:
|
|
27
|
+
type: 'text',
|
|
28
28
|
text: message_per_priority,
|
|
29
29
|
parameter_name: parameter_name
|
|
30
30
|
}
|
|
@@ -32,7 +32,7 @@ module Warb
|
|
|
32
32
|
|
|
33
33
|
def build_template_positional_parameter
|
|
34
34
|
{
|
|
35
|
-
type:
|
|
35
|
+
type: 'text',
|
|
36
36
|
text: message_per_priority
|
|
37
37
|
}
|
|
38
38
|
end
|
data/lib/warb/resources/video.rb
CHANGED
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Warb
|
|
4
|
+
class Response
|
|
5
|
+
attr_reader :input, :wa_id, :message_id, :body
|
|
6
|
+
|
|
7
|
+
def initialize(body)
|
|
8
|
+
@body = body || {}
|
|
9
|
+
extract_contact_data
|
|
10
|
+
extract_message_data
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
private
|
|
14
|
+
|
|
15
|
+
def extract_contact_data
|
|
16
|
+
first_contact = contacts&.first
|
|
17
|
+
@input = first_contact&.dig('input')
|
|
18
|
+
@wa_id = first_contact&.dig('wa_id')
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
def extract_message_data
|
|
22
|
+
@message_id = messages&.first&.dig('id')
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
def contacts
|
|
26
|
+
@body['contacts']
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
def messages
|
|
30
|
+
@body['messages']
|
|
31
|
+
end
|
|
32
|
+
end
|
|
33
|
+
end
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Warb
|
|
4
|
+
class ResponseErrorHandler
|
|
5
|
+
def initialize(body, status)
|
|
6
|
+
@body = body
|
|
7
|
+
@status = status
|
|
8
|
+
end
|
|
9
|
+
|
|
10
|
+
def handle
|
|
11
|
+
raise Warb::RequestError, 'invalid body' if @body.nil?
|
|
12
|
+
|
|
13
|
+
http_class = Warb::HTTP_ERRORS[@status]
|
|
14
|
+
error_class = custom_class || http_class || Warb::RequestError
|
|
15
|
+
Warb.configuration.logger.error(message.to_s)
|
|
16
|
+
|
|
17
|
+
raise error_class, message_from_error
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
private
|
|
21
|
+
|
|
22
|
+
def custom_class
|
|
23
|
+
Warb.configuration.custom_errors[@status]&.dig(code)
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
def message_from_error
|
|
27
|
+
details ? "(##{code}) #{details}" : message
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
def code
|
|
31
|
+
@code ||= @body.dig('error', 'code')
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
def message
|
|
35
|
+
@message ||= @body.dig('error', 'message')
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
def details
|
|
39
|
+
@details ||= @body.dig('error', 'error_data', 'details')
|
|
40
|
+
end
|
|
41
|
+
end
|
|
42
|
+
end
|
|
@@ -1,10 +1,12 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
1
3
|
module Warb
|
|
2
4
|
class TemplateDispatcher < Dispatcher
|
|
3
5
|
def list(**args)
|
|
4
6
|
filter = args.slice(:limit, :fields, :after, :before)
|
|
5
|
-
filter[:fields] = filter[:fields].join(
|
|
7
|
+
filter[:fields] = filter[:fields].join(',') if filter[:fields].is_a?(Array)
|
|
6
8
|
|
|
7
|
-
@client.get(
|
|
9
|
+
@client.get('message_templates', endpoint_prefix: :business_id, data: filter).body
|
|
8
10
|
end
|
|
9
11
|
end
|
|
10
12
|
end
|
data/lib/warb/utils.rb
CHANGED
data/lib/warb/version.rb
CHANGED
data/lib/warb.rb
CHANGED
|
@@ -1,49 +1,57 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
-
require
|
|
4
|
-
require
|
|
5
|
-
require_relative
|
|
6
|
-
require_relative
|
|
7
|
-
require_relative
|
|
8
|
-
require_relative
|
|
9
|
-
require_relative
|
|
10
|
-
require_relative
|
|
11
|
-
require_relative
|
|
12
|
-
require_relative
|
|
13
|
-
require_relative
|
|
14
|
-
require_relative
|
|
15
|
-
require_relative
|
|
16
|
-
require_relative
|
|
17
|
-
require_relative
|
|
18
|
-
require_relative
|
|
19
|
-
require_relative
|
|
20
|
-
require_relative
|
|
21
|
-
require_relative
|
|
22
|
-
require_relative
|
|
23
|
-
require_relative
|
|
24
|
-
require_relative
|
|
25
|
-
require_relative
|
|
26
|
-
require_relative
|
|
27
|
-
require_relative
|
|
28
|
-
require_relative
|
|
29
|
-
require_relative
|
|
30
|
-
require_relative
|
|
31
|
-
require_relative
|
|
32
|
-
require_relative
|
|
33
|
-
require_relative
|
|
34
|
-
require_relative
|
|
35
|
-
require_relative
|
|
36
|
-
require_relative
|
|
37
|
-
require_relative
|
|
38
|
-
require_relative
|
|
39
|
-
require_relative
|
|
3
|
+
require 'faraday'
|
|
4
|
+
require 'faraday/multipart'
|
|
5
|
+
require_relative 'warb/version'
|
|
6
|
+
require_relative 'warb/language'
|
|
7
|
+
require_relative 'warb/configuration'
|
|
8
|
+
require_relative 'warb/dispatcher_concern'
|
|
9
|
+
require_relative 'warb/client'
|
|
10
|
+
require_relative 'warb/errors'
|
|
11
|
+
require_relative 'warb/response_error_handler'
|
|
12
|
+
require_relative 'warb/response'
|
|
13
|
+
require_relative 'warb/resources/resource'
|
|
14
|
+
require_relative 'warb/resources/text'
|
|
15
|
+
require_relative 'warb/resources/image'
|
|
16
|
+
require_relative 'warb/resources/video'
|
|
17
|
+
require_relative 'warb/resources/sticker'
|
|
18
|
+
require_relative 'warb/resources/audio'
|
|
19
|
+
require_relative 'warb/resources/document'
|
|
20
|
+
require_relative 'warb/resources/location'
|
|
21
|
+
require_relative 'warb/resources/reaction'
|
|
22
|
+
require_relative 'warb/resources/location_request'
|
|
23
|
+
require_relative 'warb/resources/interactive_reply_button'
|
|
24
|
+
require_relative 'warb/resources/interactive_list'
|
|
25
|
+
require_relative 'warb/resources/interactive_call_to_action_url'
|
|
26
|
+
require_relative 'warb/resources/contact'
|
|
27
|
+
require_relative 'warb/resources/template'
|
|
28
|
+
require_relative 'warb/resources/currency'
|
|
29
|
+
require_relative 'warb/resources/date_time'
|
|
30
|
+
require_relative 'warb/resources/flow'
|
|
31
|
+
require_relative 'warb/dispatcher'
|
|
32
|
+
require_relative 'warb/media_dispatcher'
|
|
33
|
+
require_relative 'warb/template_dispatcher'
|
|
34
|
+
require_relative 'warb/indicator_dispatcher'
|
|
35
|
+
require_relative 'warb/utils'
|
|
36
|
+
require_relative 'warb/components/component'
|
|
37
|
+
require_relative 'warb/components/button'
|
|
38
|
+
require_relative 'warb/components/quick_reply_button'
|
|
39
|
+
require_relative 'warb/components/url_button'
|
|
40
|
+
require_relative 'warb/components/copy_code_button'
|
|
41
|
+
require_relative 'warb/components/voice_call_button'
|
|
42
|
+
require_relative 'warb/components/action'
|
|
40
43
|
|
|
41
44
|
module Warb
|
|
42
|
-
MESSAGING_PRODUCT =
|
|
43
|
-
RECIPIENT_TYPE =
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
45
|
+
MESSAGING_PRODUCT = 'whatsapp'
|
|
46
|
+
RECIPIENT_TYPE = 'individual'
|
|
47
|
+
HTTP_ERRORS = {
|
|
48
|
+
400 => BadRequest,
|
|
49
|
+
401 => Unauthorized,
|
|
50
|
+
403 => Forbidden,
|
|
51
|
+
404 => NotFound,
|
|
52
|
+
500 => InternalServerError,
|
|
53
|
+
503 => ServiceUnavailable
|
|
54
|
+
}.freeze
|
|
47
55
|
|
|
48
56
|
class << self
|
|
49
57
|
include DispatcherConcern
|
metadata
CHANGED
|
@@ -1,13 +1,14 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: warb
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.1.
|
|
4
|
+
version: 0.1.3
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Rebase
|
|
8
|
+
autorequire:
|
|
8
9
|
bindir: exe
|
|
9
10
|
cert_chain: []
|
|
10
|
-
date:
|
|
11
|
+
date: 2025-08-22 00:00:00.000000000 Z
|
|
11
12
|
dependencies:
|
|
12
13
|
- !ruby/object:Gem::Dependency
|
|
13
14
|
name: faraday
|
|
@@ -121,6 +122,7 @@ files:
|
|
|
121
122
|
- lib/warb/connection.rb
|
|
122
123
|
- lib/warb/dispatcher.rb
|
|
123
124
|
- lib/warb/dispatcher_concern.rb
|
|
125
|
+
- lib/warb/errors.rb
|
|
124
126
|
- lib/warb/indicator_dispatcher.rb
|
|
125
127
|
- lib/warb/language.rb
|
|
126
128
|
- lib/warb/media_dispatcher.rb
|
|
@@ -142,6 +144,8 @@ files:
|
|
|
142
144
|
- lib/warb/resources/template.rb
|
|
143
145
|
- lib/warb/resources/text.rb
|
|
144
146
|
- lib/warb/resources/video.rb
|
|
147
|
+
- lib/warb/response.rb
|
|
148
|
+
- lib/warb/response_error_handler.rb
|
|
145
149
|
- lib/warb/template_dispatcher.rb
|
|
146
150
|
- lib/warb/utils.rb
|
|
147
151
|
- lib/warb/version.rb
|
|
@@ -152,6 +156,8 @@ metadata:
|
|
|
152
156
|
homepage_uri: https://github.com/Rebase-BR/warb
|
|
153
157
|
source_code_uri: https://github.com/Rebase-BR/warb
|
|
154
158
|
changelog_uri: https://github.com/Rebase-BR/warb/blob/main/CHANGELOG.md?ref_type=heads
|
|
159
|
+
rubygems_mfa_required: 'true'
|
|
160
|
+
post_install_message:
|
|
155
161
|
rdoc_options: []
|
|
156
162
|
require_paths:
|
|
157
163
|
- lib
|
|
@@ -166,7 +172,8 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
|
166
172
|
- !ruby/object:Gem::Version
|
|
167
173
|
version: '0'
|
|
168
174
|
requirements: []
|
|
169
|
-
rubygems_version: 3.
|
|
175
|
+
rubygems_version: 3.4.10
|
|
176
|
+
signing_key:
|
|
170
177
|
specification_version: 4
|
|
171
178
|
summary: A simple yet powerfull whatsapp api wrapper
|
|
172
179
|
test_files: []
|