line |
stmt |
bran |
cond |
sub |
pod |
time |
code |
1
|
|
|
|
|
|
|
package Dubber::API; |
2
|
|
|
|
|
|
|
|
3
|
|
|
|
|
|
|
# ABSTRACT: Interact with the Dubber Call Recording platform API |
4
|
|
|
|
|
|
|
|
5
|
1
|
|
|
1
|
|
608
|
use strict; |
|
1
|
|
|
|
|
2
|
|
|
1
|
|
|
|
|
25
|
|
6
|
1
|
|
|
1
|
|
3
|
use warnings; |
|
1
|
|
|
|
|
2
|
|
|
1
|
|
|
|
|
39
|
|
7
|
|
|
|
|
|
|
|
8
|
|
|
|
|
|
|
our $VERSION = '0.011'; # VERSION |
9
|
|
|
|
|
|
|
our $AUTHORITY = 'cpan:NIGELM'; # AUTHORITY |
10
|
|
|
|
|
|
|
|
11
|
1
|
|
|
1
|
|
397
|
use Mouse; |
|
1
|
|
|
|
|
23396
|
|
|
1
|
|
|
|
|
3
|
|
12
|
1
|
|
|
1
|
|
944
|
use Method::Signatures; |
|
1
|
|
|
|
|
37296
|
|
|
1
|
|
|
|
|
8
|
|
13
|
1
|
|
|
1
|
|
363
|
use Cpanel::JSON::XS; |
|
1
|
|
|
|
|
2
|
|
|
1
|
|
|
|
|
60
|
|
14
|
1
|
|
|
1
|
|
433
|
use Crypt::Digest::MD5 qw[md5_b64]; |
|
1
|
|
|
|
|
3859
|
|
|
1
|
|
|
|
|
50
|
|
15
|
1
|
|
|
1
|
|
403
|
use Crypt::Digest::SHA256 qw[sha256_hex]; |
|
1
|
|
|
|
|
529
|
|
|
1
|
|
|
|
|
46
|
|
16
|
1
|
|
|
1
|
|
730
|
use DateTime; |
|
1
|
|
|
|
|
411182
|
|
|
1
|
|
|
|
|
56
|
|
17
|
1
|
|
|
1
|
|
525
|
use HTTP::Request; |
|
1
|
|
|
|
|
14732
|
|
|
1
|
|
|
|
|
42
|
|
18
|
1
|
|
|
1
|
|
419
|
use LWP::ConnCache; |
|
1
|
|
|
|
|
1093
|
|
|
1
|
|
|
|
|
30
|
|
19
|
1
|
|
|
1
|
|
614
|
use LWP::UserAgent; |
|
1
|
|
|
|
|
19502
|
|
|
1
|
|
|
|
|
35
|
|
20
|
1
|
|
|
1
|
|
9
|
use Try::Tiny; |
|
1
|
|
|
|
|
2
|
|
|
1
|
|
|
|
|
55
|
|
21
|
1
|
|
|
1
|
|
5
|
use URI; |
|
1
|
|
|
|
|
12
|
|
|
1
|
|
|
|
|
192
|
|
22
|
|
|
|
|
|
|
|
23
|
|
|
|
|
|
|
with 'Web::API'; |
24
|
|
|
|
|
|
|
|
25
|
|
|
|
|
|
|
# ------------------------------------------------------------------------ |
26
|
|
|
|
|
|
|
|
27
|
|
|
|
|
|
|
|
28
|
|
|
|
|
|
|
# ------------------------------------------------------------------------ |
29
|
|
|
|
|
|
|
|
30
|
|
|
|
|
|
|
has api_version => ( |
31
|
|
|
|
|
|
|
is => 'ro', |
32
|
|
|
|
|
|
|
isa => 'Num', |
33
|
|
|
|
|
|
|
default => sub {'1'}, |
34
|
|
|
|
|
|
|
); |
35
|
|
|
|
|
|
|
|
36
|
|
|
|
|
|
|
has region => ( |
37
|
|
|
|
|
|
|
is => 'ro', |
38
|
|
|
|
|
|
|
isa => 'Str', |
39
|
|
|
|
|
|
|
default => sub {'sandbox'}, |
40
|
|
|
|
|
|
|
); |
41
|
|
|
|
|
|
|
|
42
|
|
|
|
|
|
|
# ------------------------------------------------------------------------ |
43
|
|
|
|
|
|
|
|
44
|
|
|
|
|
|
|
has client_id => (is => 'ro', isa=> 'Str', required => 1); |
45
|
|
|
|
|
|
|
has client_secret => (is => 'ro', isa=> 'Str', required => 1); |
46
|
|
|
|
|
|
|
has auth_id => (is => 'ro', isa=> 'Str', required => 1); |
47
|
|
|
|
|
|
|
has auth_secret => (is => 'ro', isa=> 'Str', required => 1); |
48
|
|
|
|
|
|
|
has max_part_size => (is => 'ro', isa=> 'Int', default => sub { 5 * 1024 * 1024}); |
49
|
|
|
|
|
|
|
# ------------------------------------------------------------------------ |
50
|
|
|
|
|
|
|
has _auth_token => (is => 'rw', isa => 'Str', predicate => '_has_auth_token', clearer => 'clear_auth_token',); |
51
|
|
|
|
|
|
|
has _auth_refresh_token => (is => 'rw', isa => 'Str',); |
52
|
|
|
|
|
|
|
has _auth_token_expiry => (is => 'rw', isa => 'DateTime',); |
53
|
|
|
|
|
|
|
|
54
|
1
|
0
|
|
1
|
|
93766
|
method _new_auth_token ($refresh_token?) { |
|
0
|
|
|
0
|
|
|
|
|
0
|
|
|
|
|
|
|
|
0
|
|
|
|
|
|
|
55
|
0
|
|
|
|
|
|
my $uri = URI->new( $self->base_url . '/token' ); |
56
|
0
|
|
|
|
|
|
my $request = HTTP::Request->new( 'POST', $uri ); |
57
|
0
|
|
|
|
|
|
$request->header( |
58
|
|
|
|
|
|
|
'Accept' => 'application/json', |
59
|
|
|
|
|
|
|
'Content-type' => 'application/x-www-form-urlencoded' |
60
|
|
|
|
|
|
|
); |
61
|
|
|
|
|
|
|
|
62
|
|
|
|
|
|
|
# token request content |
63
|
0
|
|
|
|
|
|
my $content = { client_id => $self->client_id, client_secret => $self->client_secret }; |
64
|
0
|
0
|
|
|
|
|
if ($refresh_token) { |
65
|
|
|
|
|
|
|
|
66
|
|
|
|
|
|
|
# refresh request content... |
67
|
0
|
|
|
|
|
|
$content->{refresh_token} = $refresh_token; |
68
|
0
|
|
|
|
|
|
$content->{grant_type} = 'refresh_token'; |
69
|
|
|
|
|
|
|
} |
70
|
|
|
|
|
|
|
else { |
71
|
0
|
|
|
|
|
|
$content->{username} = $self->auth_id; |
72
|
0
|
|
|
|
|
|
$content->{password} = $self->auth_secret; |
73
|
0
|
|
|
|
|
|
$content->{grant_type} = 'password'; |
74
|
|
|
|
|
|
|
} |
75
|
0
|
|
|
|
|
|
$request->content( $self->encode( $content, 'application/x-www-form-urlencoded' ) ); |
76
|
|
|
|
|
|
|
|
77
|
|
|
|
|
|
|
# send and decode query |
78
|
0
|
|
|
|
|
|
$self->_clear_state; |
79
|
0
|
|
|
|
|
|
my $response = $self->request($request); |
80
|
0
|
|
|
|
|
|
my $answer = $self->format_response($response); |
81
|
0
|
|
|
|
|
|
$self->_clear_state; |
82
|
|
|
|
|
|
|
|
83
|
|
|
|
|
|
|
# unpack components |
84
|
0
|
|
|
|
|
|
$self->_auth_token( $answer->{content}{access_token} ); |
85
|
0
|
|
|
|
|
|
$self->_auth_refresh_token( $answer->{content}{refresh_token} ); |
86
|
0
|
|
|
|
|
|
$self->_auth_token_expiry( DateTime->now->add( seconds => $answer->{content}{expires_in} - 20 ) ); |
87
|
|
|
|
|
|
|
|
88
|
|
|
|
|
|
|
# return the token |
89
|
0
|
|
|
|
|
|
return $answer->{content}{access_token}; |
90
|
|
|
|
|
|
|
} |
91
|
|
|
|
|
|
|
|
92
|
|
|
|
|
|
|
# ------------------------------------------------------------------------ |
93
|
1
|
0
|
|
1
|
|
926
|
method is_authenticated () { |
|
0
|
|
|
0
|
|
|
|
|
0
|
|
|
|
|
|
|
94
|
0
|
0
|
0
|
|
|
|
return 1 if ( ( $self->_has_auth_token ) and ( DateTime->now < $self->_auth_token_expiry ) ); |
95
|
0
|
|
|
|
|
|
return; |
96
|
|
|
|
|
|
|
} |
97
|
|
|
|
|
|
|
|
98
|
|
|
|
|
|
|
# ------------------------------------------------------------------------ |
99
|
1
|
0
|
|
1
|
|
652
|
method auth_token () { |
|
0
|
|
|
0
|
|
|
|
|
0
|
|
|
|
|
|
|
100
|
0
|
0
|
|
|
|
|
if ( $self->_has_auth_token ) { |
101
|
0
|
0
|
|
|
|
|
if ( DateTime->now > $self->_auth_token_expiry ) { |
102
|
0
|
|
|
|
|
|
$self->_new_auth_token( $self->_auth_refresh_token ); |
103
|
|
|
|
|
|
|
} |
104
|
|
|
|
|
|
|
} |
105
|
|
|
|
|
|
|
else { |
106
|
0
|
|
|
|
|
|
$self->_new_auth_token(); |
107
|
|
|
|
|
|
|
} |
108
|
0
|
|
|
|
|
|
return $self->_auth_token; |
109
|
|
|
|
|
|
|
} |
110
|
|
|
|
|
|
|
|
111
|
|
|
|
|
|
|
# ------------------------------------------------------------------------ |
112
|
1
|
0
|
|
1
|
|
719
|
method auth_lifetime_seconds () { |
|
0
|
|
|
0
|
|
|
|
|
0
|
|
|
|
|
|
|
113
|
0
|
0
|
|
|
|
|
return 0 unless ( $self->is_authenticated ); |
114
|
0
|
|
|
|
|
|
my $diff = $self->_auth_token_expiry->delta_ms( DateTime->now ); |
115
|
0
|
|
|
|
|
|
return ( abs( $diff->minutes * 60 ) + abs( $diff->seconds ) ); |
116
|
|
|
|
|
|
|
} |
117
|
|
|
|
|
|
|
|
118
|
|
|
|
|
|
|
# ------------------------------------------------------------------------ |
119
|
|
|
|
|
|
|
has header => ( |
120
|
|
|
|
|
|
|
is => 'rw', |
121
|
|
|
|
|
|
|
isa => 'HashRef', |
122
|
|
|
|
|
|
|
lazy_build => 1, |
123
|
|
|
|
|
|
|
); |
124
|
|
|
|
|
|
|
|
125
|
1
|
0
|
|
1
|
|
680
|
method _build_header () { |
|
0
|
|
|
0
|
|
|
|
|
0
|
|
|
|
|
|
|
126
|
0
|
|
|
|
|
|
return { Authorization => 'Bearer ' . $self->auth_token }; |
127
|
|
|
|
|
|
|
} |
128
|
|
|
|
|
|
|
|
129
|
|
|
|
|
|
|
# ------------------------------------------------------------------------ |
130
|
|
|
|
|
|
|
has connection_cache => ( |
131
|
|
|
|
|
|
|
is => 'ro', |
132
|
|
|
|
|
|
|
isa => 'LWP::ConnCache', |
133
|
|
|
|
|
|
|
lazy_build => 1, |
134
|
|
|
|
|
|
|
); |
135
|
|
|
|
|
|
|
|
136
|
1
|
0
|
|
1
|
|
646
|
method _build_connection_cache () { return LWP::ConnCache->new( total_capacity => 5 ); } |
|
0
|
|
|
0
|
|
|
|
|
0
|
|
|
|
|
|
|
|
0
|
|
|
|
|
|
|
137
|
|
|
|
|
|
|
|
138
|
|
|
|
|
|
|
# ------------------------------------------------------------------------ |
139
|
|
|
|
|
|
|
has json_coder => ( |
140
|
|
|
|
|
|
|
is => 'ro', |
141
|
|
|
|
|
|
|
isa => 'Cpanel::JSON::XS', |
142
|
|
|
|
|
|
|
lazy_build => 1, |
143
|
|
|
|
|
|
|
); |
144
|
|
|
|
|
|
|
|
145
|
1
|
0
|
|
1
|
|
615
|
method _build_json_coder () { return Cpanel::JSON::XS->new->utf8; } |
|
0
|
|
|
0
|
|
|
|
|
0
|
|
|
|
|
|
|
|
0
|
|
|
|
|
|
|
146
|
|
|
|
|
|
|
|
147
|
|
|
|
|
|
|
# ------------------------------------------------------------------------ |
148
|
|
|
|
|
|
|
has endpoints => ( |
149
|
|
|
|
|
|
|
is => 'ro', |
150
|
|
|
|
|
|
|
default => sub { |
151
|
|
|
|
|
|
|
{ root => { path => '/' }, |
152
|
|
|
|
|
|
|
|
153
|
|
|
|
|
|
|
# Group Methods (Group Authentication Required) |
154
|
|
|
|
|
|
|
get_group_details => { path => 'groups/:group_id' }, |
155
|
|
|
|
|
|
|
get_group_accounts => { path => 'groups/:group_id/accounts' }, |
156
|
|
|
|
|
|
|
create_child_group => { path => 'groups/:group_id/groups', method => 'POST' }, |
157
|
|
|
|
|
|
|
create_account => { path => 'accounts', method => 'POST' }, |
158
|
|
|
|
|
|
|
get_group_unidentified_recordings => { path => 'groups/:group_id/unidentified_recordings' }, |
159
|
|
|
|
|
|
|
create_group_unidentified_recording => |
160
|
|
|
|
|
|
|
{ path => 'groups/:group_id/unidentified_recordings', method => 'POST' }, |
161
|
|
|
|
|
|
|
|
162
|
|
|
|
|
|
|
# Account Methods |
163
|
|
|
|
|
|
|
get_account_details => { path => 'accounts/:account_id' }, |
164
|
|
|
|
|
|
|
update_account_details => { path => 'accounts/:account_id', method => 'PUT' }, |
165
|
|
|
|
|
|
|
|
166
|
|
|
|
|
|
|
# Recording Methods |
167
|
|
|
|
|
|
|
get_account_recordings => { path => 'accounts/:account_id/recordings', method => 'GET' }, |
168
|
|
|
|
|
|
|
create_recording => { path => 'accounts/:account_id/recordings', method => 'POST' }, |
169
|
|
|
|
|
|
|
get_recording_details => { path => 'recordings/:recording_id', method => 'GET' }, |
170
|
|
|
|
|
|
|
get_recording_waveform => { path => 'recordings/:recording_id/waveform', method => 'GET' }, |
171
|
|
|
|
|
|
|
delete_recording => { path => 'recordings/:recording_id', method => 'DELETE' }, |
172
|
|
|
|
|
|
|
update_recording_metadata => { path => 'recordings/:recording_id/metadata', method => 'PUT' }, |
173
|
|
|
|
|
|
|
add_recording_tags => { path => 'recordings/:recording_id/tags', method => 'POST' }, |
174
|
|
|
|
|
|
|
delete_recording_tags => { path => 'recordings/:recording_id/tags', method => 'DELETE' }, |
175
|
|
|
|
|
|
|
|
176
|
|
|
|
|
|
|
# Multipart Recording Methods |
177
|
|
|
|
|
|
|
create_multipart_recording => { path => 'accounts/:account_id/recordings', method => 'POST' }, |
178
|
|
|
|
|
|
|
get_recording_upload_part => { |
179
|
|
|
|
|
|
|
path => 'recordings/:recording_id/upload', |
180
|
|
|
|
|
|
|
method => 'GET', |
181
|
|
|
|
|
|
|
|
182
|
|
|
|
|
|
|
#mandatory => [qw(part_number content_md5 content_sha256)] |
183
|
|
|
|
|
|
|
}, |
184
|
|
|
|
|
|
|
put_complete_multipart_recording_upload => |
185
|
|
|
|
|
|
|
|
186
|
|
|
|
|
|
|
#{ path => 'recordings/:recording_id/complete_upload', method => 'PUT', mandatory => [qw(parts)] }, |
187
|
|
|
|
|
|
|
{ path => 'recordings/:recording_id/complete_upload', method => 'PUT', }, |
188
|
|
|
|
|
|
|
abort_multipart_recording_upload => { path => 'recordings/:recording_id', method => 'DELETE' }, |
189
|
|
|
|
|
|
|
|
190
|
|
|
|
|
|
|
# User Methods |
191
|
|
|
|
|
|
|
get_account_users => { path => 'accounts/:account_id/users', method => 'GET' }, |
192
|
|
|
|
|
|
|
create_account_user => { path => 'accounts/:account_id/users', method => 'POST' }, |
193
|
|
|
|
|
|
|
get_user_details => { path => 'users/:user_id', method => 'GET' }, |
194
|
|
|
|
|
|
|
delete_user => { path => 'users/:user_id', method => 'DELETE' }, |
195
|
|
|
|
|
|
|
update_user => { path => 'users/:user_id', method => 'PUT' }, |
196
|
|
|
|
|
|
|
|
197
|
|
|
|
|
|
|
# Profile Methods |
198
|
|
|
|
|
|
|
get_profile => { path => 'profile', method => 'GET' }, |
199
|
|
|
|
|
|
|
|
200
|
|
|
|
|
|
|
# Notification (Rest Hook) Methods |
201
|
|
|
|
|
|
|
create_group_notification => { path => 'groups/:group_id/notifications', method => 'POST' } |
202
|
|
|
|
|
|
|
, # (Group Authentication Only) |
203
|
|
|
|
|
|
|
get_group_notifications => { path => 'groups/:group_id/notifications', method => 'GET' } |
204
|
|
|
|
|
|
|
, # (Group Authentication Only) |
205
|
|
|
|
|
|
|
create_account_notification => { path => 'accounts/:account_id/notifications', method => 'POST' }, |
206
|
|
|
|
|
|
|
get_account_notifications => { path => 'accounts/:account_id/notifications', method => 'GET' }, |
207
|
|
|
|
|
|
|
get_notification_details => { path => 'notifications/:notification_id', method => 'GET' }, |
208
|
|
|
|
|
|
|
update_notification => { path => 'notifications/:notification_id', method => 'PUT' }, |
209
|
|
|
|
|
|
|
activate_notification => { path => 'notifications/:notification_id/activate', method => 'POST' }, |
210
|
|
|
|
|
|
|
release_unclaimed_notification => { path => 'notifications/:notification_id/unclaimed', method => 'GET' }, |
211
|
|
|
|
|
|
|
delete_notification => { path => 'notifications/:notification_id', method => 'DELETE' }, |
212
|
|
|
|
|
|
|
|
213
|
|
|
|
|
|
|
#Â Dub.Point (Group Authentication Required) |
214
|
|
|
|
|
|
|
get_group_unidentified_dub_points => |
215
|
|
|
|
|
|
|
{ path => 'groups/:group_id/unidentified_dub_points', method => 'GET' }, |
216
|
|
|
|
|
|
|
get_account_dub_points => { path => 'accounts/:account_id/dub_points', method => 'GET' }, |
217
|
|
|
|
|
|
|
create_account_dub_point => { path => 'accounts/:account_id/dub_points', method => 'POST' }, |
218
|
|
|
|
|
|
|
get_dub_point_details => { path => 'dub_points/:dub_point_id', method => 'GET' }, |
219
|
|
|
|
|
|
|
find_dub_point => { |
220
|
|
|
|
|
|
|
path => 'dub_points/find', |
221
|
|
|
|
|
|
|
method => 'GET', |
222
|
|
|
|
|
|
|
mandatory => [qw(external_type service_provider external_group external_identifier)] |
223
|
|
|
|
|
|
|
}, |
224
|
|
|
|
|
|
|
|
225
|
|
|
|
|
|
|
# OAuth 2 Methods |
226
|
|
|
|
|
|
|
revoke_access_token => { path => 'revoke', method => 'POST' }, |
227
|
|
|
|
|
|
|
|
228
|
|
|
|
|
|
|
}; |
229
|
|
|
|
|
|
|
}, |
230
|
|
|
|
|
|
|
); |
231
|
|
|
|
|
|
|
|
232
|
1
|
0
|
|
1
|
|
856
|
method commands () { return $self->endpoints; } |
|
0
|
|
|
0
|
|
|
|
|
0
|
|
|
|
|
|
|
|
0
|
|
|
|
|
|
|
233
|
|
|
|
|
|
|
|
234
|
|
|
|
|
|
|
# ------------------------------------------------------------------------ |
235
|
1
|
0
|
|
1
|
|
3427
|
method upload_recording_mp3_file ($account_id, $call_metadata, $mp3_file_or_data) { |
|
0
|
0
|
|
0
|
|
|
|
|
0
|
0
|
|
|
|
|
|
|
0
|
0
|
|
|
|
|
|
|
0
|
|
|
|
|
|
|
|
0
|
|
|
|
|
|
|
|
0
|
|
|
|
|
|
|
|
0
|
|
|
|
|
|
|
|
0
|
|
|
|
|
|
|
236
|
0
|
|
|
|
|
|
my @data_parts = $self->_split_recording_data($mp3_file_or_data); |
237
|
|
|
|
|
|
|
|
238
|
|
|
|
|
|
|
# create the recording object |
239
|
0
|
|
|
|
|
|
my $res = $self->create_recording( account_id => $account_id, %{$call_metadata} ); |
|
0
|
|
|
|
|
|
|
240
|
0
|
0
|
|
|
|
|
if ( $res->{code} eq '201' ) { |
241
|
0
|
|
|
|
|
|
my $recording_id = $res->{content}{id}; |
242
|
0
|
|
|
|
|
|
my $return_status; |
243
|
|
|
|
|
|
|
try { |
244
|
0
|
|
|
0
|
|
|
my @etag_parts; |
245
|
0
|
|
|
|
|
|
my $part_number = 0; |
246
|
0
|
|
|
|
|
|
foreach my $part_data (@data_parts) { |
247
|
0
|
|
|
|
|
|
my $md5_b64 = md5_b64($part_data); |
248
|
0
|
|
|
|
|
|
my $sha256_hex = sha256_hex($part_data); |
249
|
0
|
|
|
|
|
|
++$part_number; |
250
|
|
|
|
|
|
|
|
251
|
|
|
|
|
|
|
# request to upload a recording part |
252
|
0
|
|
|
|
|
|
my $upload_req_res = $self->get_recording_upload_part( |
253
|
|
|
|
|
|
|
recording_id => $recording_id, |
254
|
|
|
|
|
|
|
part_number => $part_number, |
255
|
|
|
|
|
|
|
content_md5 => $md5_b64, |
256
|
|
|
|
|
|
|
content_sha256 => $sha256_hex |
257
|
|
|
|
|
|
|
); |
258
|
0
|
0
|
|
|
|
|
if ( $upload_req_res->{code} eq '200' ) { |
259
|
|
|
|
|
|
|
|
260
|
|
|
|
|
|
|
# upload the recording part |
261
|
0
|
|
|
|
|
|
my $put_uri = URI->new( $upload_req_res->{content}{url} ); |
262
|
0
|
|
|
|
|
|
my $put_request = HTTP::Request->new( 'PUT', $put_uri ); |
263
|
|
|
|
|
|
|
$put_request->header( |
264
|
|
|
|
|
|
|
'Accept' => 'application/json', |
265
|
|
|
|
|
|
|
'Content-type' => 'audio/mpeg', # apparently right! |
266
|
|
|
|
|
|
|
'Authorization' => $upload_req_res->{content}{authorization}, |
267
|
|
|
|
|
|
|
'X-Amz-Date' => $upload_req_res->{content}{'X-Amz-Date'}, |
268
|
|
|
|
|
|
|
'Host' => $upload_req_res->{content}{Host}, |
269
|
0
|
|
|
|
|
|
'Content-Md5' => $md5_b64, |
270
|
|
|
|
|
|
|
'X-Amz-Content-Sha256' => $sha256_hex |
271
|
|
|
|
|
|
|
); |
272
|
0
|
|
|
|
|
|
$put_request->content($part_data); |
273
|
0
|
|
|
|
|
|
my $put_response = $self->request($put_request); |
274
|
0
|
0
|
|
|
|
|
if ( $put_response->is_success ) { |
275
|
0
|
|
|
|
|
|
push( @etag_parts, { part_number => $part_number, e_tag => $put_response->header('ETag') } ); |
276
|
|
|
|
|
|
|
} |
277
|
|
|
|
|
|
|
else { |
278
|
0
|
|
|
|
|
|
die "Unable to PUT recording upload - $!"; |
279
|
|
|
|
|
|
|
} |
280
|
|
|
|
|
|
|
} |
281
|
|
|
|
|
|
|
else { |
282
|
0
|
|
|
|
|
|
die "Unable to request recording upload - $!"; |
283
|
|
|
|
|
|
|
} |
284
|
|
|
|
|
|
|
} |
285
|
0
|
|
|
|
|
|
$return_status = $self->put_complete_multipart_recording_upload( |
286
|
|
|
|
|
|
|
recording_id => $recording_id, |
287
|
|
|
|
|
|
|
parts => \@etag_parts |
288
|
|
|
|
|
|
|
); |
289
|
|
|
|
|
|
|
} |
290
|
|
|
|
|
|
|
catch { |
291
|
|
|
|
|
|
|
# it failed - delete the part uploaded chunk |
292
|
0
|
|
|
0
|
|
|
$self->abort_multipart_recording_upload( recording_id => $recording_id ); |
293
|
0
|
|
|
|
|
|
}; |
294
|
0
|
|
|
|
|
|
return $return_status; |
295
|
|
|
|
|
|
|
} |
296
|
0
|
|
|
|
|
|
return $res; |
297
|
|
|
|
|
|
|
} |
298
|
|
|
|
|
|
|
|
299
|
|
|
|
|
|
|
# ------------------------------------------------------------------------ |
300
|
1
|
0
|
|
1
|
|
2499
|
method _split_recording_data ($file_or_data) { |
|
0
|
0
|
|
0
|
|
|
|
|
0
|
|
|
|
|
|
|
|
0
|
|
|
|
|
|
|
|
0
|
|
|
|
|
|
|
301
|
0
|
|
|
|
|
|
my $data; |
302
|
0
|
0
|
0
|
|
|
|
if ( ref($file_or_data) and $file_or_data->isa('Path::Tiny') ) { |
303
|
0
|
0
|
|
|
|
|
my $stat = $file_or_data->stat or die "File $file_or_data does not exist - $!\n"; |
304
|
0
|
|
|
|
|
|
$data = $file_or_data->slurp_raw; |
305
|
|
|
|
|
|
|
} |
306
|
|
|
|
|
|
|
else { |
307
|
0
|
|
|
|
|
|
$data = $file_or_data; |
308
|
|
|
|
|
|
|
} |
309
|
|
|
|
|
|
|
|
310
|
|
|
|
|
|
|
# split into chunks of $max_part_size |
311
|
0
|
0
|
|
|
|
|
my $template = |
312
|
|
|
|
|
|
|
sprintf( 'A%d', $self->max_part_size ) x int( length($data) / $self->max_part_size ) |
313
|
|
|
|
|
|
|
. ( length($data) % $self->max_part_size ) |
314
|
|
|
|
|
|
|
? 'A*' |
315
|
|
|
|
|
|
|
: ''; |
316
|
0
|
|
|
|
|
|
my @chunks = unpack( $template, $data ); |
317
|
0
|
|
|
|
|
|
return @chunks; |
318
|
|
|
|
|
|
|
} |
319
|
|
|
|
|
|
|
|
320
|
|
|
|
|
|
|
# ------------------------------------------------------------------------ |
321
|
1
|
0
|
|
1
|
|
2010
|
method BUILD ($args) { |
|
0
|
0
|
|
0
|
|
|
|
|
0
|
|
|
|
|
|
|
|
0
|
|
|
|
|
|
|
|
0
|
|
|
|
|
|
|
322
|
0
|
|
0
|
|
|
|
$self->user_agent( __PACKAGE__ . ' ' . ( $Dubber::API::VERSION || '' ) ); |
323
|
0
|
|
|
|
|
|
$self->base_url( 'https://api.dubber.net/' . $self->region . '/v' . $self->api_version ); |
324
|
0
|
|
|
|
|
|
$self->content_type('application/json'); |
325
|
0
|
|
0
|
0
|
|
|
$self->decoder( sub { $self->json_coder->decode( shift || '{}' ) } ); |
|
0
|
|
|
|
|
|
|
326
|
|
|
|
|
|
|
} |
327
|
|
|
|
|
|
|
|
328
|
|
|
|
|
|
|
# ------------------------------------------------------------------------ |
329
|
1
|
0
|
|
1
|
|
755
|
method _build_agent () { |
|
0
|
|
|
0
|
|
|
|
|
0
|
|
|
|
|
|
|
330
|
0
|
|
|
|
|
|
return LWP::UserAgent->new( |
331
|
|
|
|
|
|
|
agent => $self->user_agent, |
332
|
|
|
|
|
|
|
cookie_jar => $self->cookies, |
333
|
|
|
|
|
|
|
timeout => $self->timeout, |
334
|
|
|
|
|
|
|
con_cache => $self->connection_cache, |
335
|
|
|
|
|
|
|
keep_alive => 1, |
336
|
|
|
|
|
|
|
ssl_opts => { verify_hostname => $self->strict_ssl }, |
337
|
|
|
|
|
|
|
); |
338
|
|
|
|
|
|
|
} |
339
|
|
|
|
|
|
|
|
340
|
|
|
|
|
|
|
# ------------------------------------------------------------------------ |
341
|
1
|
0
|
|
1
|
|
675
|
method _clear_state () { $self->clear_decoded_response; $self->clear_response; } |
|
0
|
|
|
0
|
|
|
|
|
0
|
|
|
|
|
|
|
|
0
|
|
|
|
|
|
|
|
0
|
|
|
|
|
|
|
342
|
|
|
|
|
|
|
|
343
|
|
|
|
|
|
|
# ------------------------------------------------------------------------ |
344
|
|
|
|
|
|
|
|
345
|
|
|
|
|
|
|
__PACKAGE__->meta->make_immutable; |
346
|
|
|
|
|
|
|
|
347
|
|
|
|
|
|
|
1; |
348
|
|
|
|
|
|
|
|
349
|
|
|
|
|
|
|
__END__ |
350
|
|
|
|
|
|
|
|
351
|
|
|
|
|
|
|
=pod |
352
|
|
|
|
|
|
|
|
353
|
|
|
|
|
|
|
=encoding UTF-8 |
354
|
|
|
|
|
|
|
|
355
|
|
|
|
|
|
|
=head1 NAME |
356
|
|
|
|
|
|
|
|
357
|
|
|
|
|
|
|
Dubber::API - Interact with the Dubber Call Recording platform API |
358
|
|
|
|
|
|
|
|
359
|
|
|
|
|
|
|
=head1 VERSION |
360
|
|
|
|
|
|
|
|
361
|
|
|
|
|
|
|
version 0.011 |
362
|
|
|
|
|
|
|
|
363
|
|
|
|
|
|
|
This is undocumented to an amazing degree at present! |
364
|
|
|
|
|
|
|
|
365
|
|
|
|
|
|
|
=head1 AUTHOR |
366
|
|
|
|
|
|
|
|
367
|
|
|
|
|
|
|
Nigel Metheringham <nigelm@cpan.org> |
368
|
|
|
|
|
|
|
|
369
|
|
|
|
|
|
|
=head1 COPYRIGHT AND LICENSE |
370
|
|
|
|
|
|
|
|
371
|
|
|
|
|
|
|
This software is copyright (c) 2017 by Nigel Metheringham. |
372
|
|
|
|
|
|
|
|
373
|
|
|
|
|
|
|
This is free software; you can redistribute it and/or modify it under |
374
|
|
|
|
|
|
|
the same terms as the Perl 5 programming language system itself. |
375
|
|
|
|
|
|
|
|
376
|
|
|
|
|
|
|
=cut |