File Coverage

blib/lib/Net/API/Telegram/Generic.pm
Criterion Covered Total %
statement 37 283 13.0
branch 0 126 0.0
condition 0 38 0.0
subroutine 13 37 35.1
pod 2 5 40.0
total 52 489 10.6


line stmt bran cond sub pod time code
1             # -*- perl -*-
2             ##----------------------------------------------------------------------------
3             ## Telegram API - ~/lib/Net/API/Telegram/Generic.pm
4             ## Version v0.100.1
5             ## Copyright(c) 2019 Jacques Deguest
6             ## Author: Jacques Deguest <@sitael.tokyo.deguest.jp>
7             ## Created 2019/06/02
8             ## Modified 2020/05/21
9             ##
10             ##----------------------------------------------------------------------------
11             package Net::API::Telegram::Generic;
12             BEGIN
13             {
14 1     1   430 use strict;
  1         3  
  1         29  
15 1     1   14 use parent qw( Module::Generic );
  1         2  
  1         4  
16 1     1   58 use Devel::StackTrace;
  1         1  
  1         20  
17 1     1   4 use Data::Dumper;
  1         2  
  1         57  
18 1     1   6 use Scalar::Util;
  1         2  
  1         42  
19 1     1   6 use DateTime;
  1         1  
  1         20  
20 1     1   4 use DateTime::TimeZone;
  1         2  
  1         28  
21 1     1   5 use File::Temp;
  1         2  
  1         76  
22 1     1   5 use File::Spec;
  1         2  
  1         17  
23             ## For the JSON::true and JSON::false
24 1     1   4 use JSON;
  1         2  
  1         16  
25 1     1   131 use Nice::Try;
  1         2  
  1         9  
26 1     1   834272 use Net::API::Telegram::Number;
  1         3  
  1         9  
27 1     1   2530 our( $VERSION ) = 'v0.100.1';
28             };
29              
30             sub init
31             {
32 0     0 1   my $self = shift( @_ );
33             ## Get the init params always present and including keys like _parent and _field
34 0           my $init = shift( @_ );
35 0           my $class = ref( $self );
36 0 0         if( Scalar::Util::blessed( $init ) )
37             {
38 0 0         if( $init->isa( 'Net::API::Telegram' ) )
39             {
40 0           $self->{ '_parent' } = $init;
41 0           $self->{ '_debug' } = $init->debug;
42             }
43             }
44             else
45             {
46 0   0       $self->{_parent} = $init->{ '_parent' } || warn( "Property '_parent' is not provided in the init hash!\n" );
47 0   0       $self->{_field} = $init->{ '_field' } || warn( "Property '_field' is not provided in the init hash!\n" );
48 0           $self->{debug} = $init->{ '_debug' };
49             }
50 0           $self->{_init_strict_use_sub} = 1;
51 0           $self->SUPER::init( @_ );
52 0           return( $self );
53             }
54              
55             sub as_hash
56             {
57 0     0 1   my $self = shift( @_ );
58 0   0       my $class = ref( $self ) || return( $self->error( "This method \"as_hash\" must be called with an object, not using class \"$self\"." ) );
59 0   0       my $anti_loop = shift( @_ ) || '_as_hash_anti_loop_' . time();
60 0           my $hash = {};
61             local $crawl = sub
62             {
63 0     0     my $this = shift( @_ );
64 0 0         if( Scalar::Util::blessed( $this ) )
    0          
    0          
65             {
66             ## $self->_message( 3, "\tvalue to check '$this' is an object of type '", ref( $this ), "'." );
67             #my $ref = $self->{ $k }->as_hash( $anti_loop );
68             #return( $ref );
69 0 0         if( $this->can( 'as_hash' ) )
    0          
    0          
70             {
71             ## $self->_message( 3, "\t\tobject can 'as_hash'" );
72 0           my $h = $this->as_hash( $anti_loop );
73             ## $self->_message( 3, "\t\tobject '", ref( $this ), "' returned value is: ", sub{ $self->dumper( $h ) } );
74 0 0         return( $h ) if( length( $h ) );
75             }
76             elsif( overload::Overloaded( $this ) )
77             {
78 0           return( "$o" );
79             }
80             elsif( $this->can( 'as_string' ) )
81             {
82 0           return( $this->as_string );
83             }
84             else
85             {
86 0           warn( "Warning only: I have an object of class \"", ref( $this ), "\" ($this), but is not overloaded and does not have an as_string method, so I don't know what to do to get a string version of it.\n" );
87             }
88             }
89             elsif( ref( $this ) eq 'ARRAY' )
90             {
91             ## $self->_message( 3, "\tvalue to check '$this' is an array reference." );
92 0           my $arr = [];
93 0           foreach my $that ( @$this )
94             {
95 0           my $v = $crawl->( $that );
96             ## $self->_message( 3, "\t\tReturned value to add to array is '$v': ", sub{ $self->dumper( $v ) } );
97 0 0         push( @$arr, $v ) if( length( $v ) );
98             }
99             ## $self->_messagef( 3, "\treturning %d items in this array.", scalar( @$arr ) );
100 0           return( $arr );
101             }
102             elsif( ref( $this ) eq 'HASH' )
103             {
104             ## $self->_message( 3, "\tvalue to check '$this' is a hash reference." );
105 0 0         return( $this ) if( exists( $this->{ $anti_loop } ) );
106 0           $this->{ $anti_loop }++;
107 0           my $ref = {};
108 0           foreach my $k ( keys( %$this ) )
109             {
110 0           $ref->{ $k } = $crawl->( $this->{ $k } );
111             }
112 0           return( $ref );
113             }
114             else
115             {
116             ## $self->_message( 3, "\tvalue to check '$this' is a scalar, returning it." );
117 0           return( $this );
118             }
119 0           };
120            
121 0           foreach my $k ( keys( %$self ) )
122             {
123 0 0         last if( exists( $self->{ $anti_loop } ) );
124             ## Only process keys if their corresponding method exists in their package
125 0 0         if( defined( &{ "${class}::${k}" } ) )
  0            
126             {
127             ## $self->_message( 3, "Getting data for $k" );
128 0 0         if( $self->_is_boolean( $k ) )
129             {
130 0 0         $hash->{ $k } = ( $self->{ $k } ? JSON::true : JSON::false );
131             ## $self->_message( 3, "\tvalue set to boolean '$hash->{$k}'" );
132             }
133             else
134             {
135 0           $hash->{ $k } = $crawl->( $self->{ $k } );
136             }
137             }
138             }
139 0           return( $hash );
140             }
141              
142             sub dumpto
143             {
144 0     0 0   my $self = shift( @_ );
145 0           my( $data, $file ) = @_;
146 0           local $Data::Dumper::Sortkeys = 1;
147 0           local $Data::Dumper::Terse = 1;
148 0           local $Data::Dumper::Indent = 1;
149 0           local $Data::Dumper::Useqq = 1;
150 0   0       my $fh = IO::File->new( ">$file" ) || die( "Unable to create file '$file': $!\n" );
151 0           $fh->print( Data::Dumper::Dumper( $data ), "\n" );
152 0           $fh->close;
153             ## 606 so it can work under command line and web alike
154 0           chmod( 0666, $file );
155 0           return( 1 );
156             }
157              
158 0     0 0   sub parent { return( shift->{_parent} ); }
159              
160             sub TO_JSON
161             {
162 0     0 0   my $self = shift( @_ );
163 0 0         return( $self->can( 'as_string' ) ? $self->as_string : $self );
164             }
165              
166             sub _download
167             {
168 0     0     my $self = shift( @_ );
169 0   0       my $id = shift( @_ ) || return( $self->error( "No file id was provided" ) );
170 0           my $opts = {};
171 0 0         $opts = shift( @_ ) if( ref( $_[0] ) eq 'HASH' );
172 0           my $parent = $self->_parent;
173             ## https://core.telegram.org/bots/api#getfile
174 0   0       my $file = $self->_parent->getFile({
175             'file_id' => $id
176             }) || return( $self->error( "Unable to get file information object for file id $id: ", $parent->error->message ) );
177 0           my $path = $file->file_path;
178 0           my $uri = URI->new( $parent->dl_uri );
179 0           $uri->path( $uri->path . '/' . $path );
180 0           my $datadir = File::Spec->tmpdir;
181 0           my $tmpdir = File::Temp::tempdir( 'telegram-file-XXXXXXX', DIR => $datadir, CLEANUP => $parent->cleanup_temp );
182             ##( $fh, $file ) = tempfile( "data-XXXXXXX", SUFFIX => ".${ext}", DIR => $tmpdir );
183 0           my $filepath = File::Temp::mktemp( "$tmpdir/data-XXXXXXX" );
184 0 0         $filepath .= '.' . $opts->{ext} if( $opts->{ext} );
185 0           my $req = JDev::HTTP::Request->new( 'GET' => $uri );
186 0           my $res = $parent->agent->request( $req, $filepath );
187 0           my $mime = $res->content_type;
188 0           my $len = $res->content_length;
189 0 0         if( !$self->is_success )
190             {
191 0           return( $self->error( sprintf( "Unable to download file \"$path\". Server returned error code %s (%s)", $res->code, $res->message ) ) );
192             }
193 0 0         if( $len != -s( $filepath ) )
194             {
195 0           warn( sprintf( "Warning only: The size in bytes returned by the server ($len) is different than the local file (%d)\n", -s( $filepath ) ) );
196             }
197 0           my $ext;
198 0 0 0       if( !$opts->{ext} && length( $mime ) )
199             {
200 0 0         if( $mime =~ /\/([^\/]+)$/ )
201             {
202 0           my $ext = $1;
203 0           rename( $filepath, "${filepath}.${ext}" );
204 0           $filepath = "${filepath}.${ext}";
205             }
206             }
207             return({
208 0           'filepath' => $filepath,
209             'mime' => $mime,
210             'response' => $res,
211             'size' => -s( $filepath ),
212             });
213             }
214              
215 0     0     sub _field { return( shift->_set_get( '_field', @_ ) ); }
216              
217             sub _get_base_class
218             {
219 0     0     my $self = shift( @_ );
220 0           my $class = shift( @_ );
221 0           my $base = __PACKAGE__;
222 0           $base =~ s/\:\:Generic$//;
223 0           my $pkg = ( $class =~ /^($base\:\:(?:[^\:]+)?)/ )[0];
224             }
225              
226             # sub _instantiate_object
227             # {
228             # my $self = shift( @_ );
229             # my $field = shift( @_ );
230             # my $class = shift( @_ );
231             # my $h =
232             # {
233             # '_parent' => $self->{ '_parent' },
234             # '_field' => $field,
235             # '_debug' => $self->{ '_debug' },
236             # };
237             # $h->{ '_dbh' } = $self->{ '_dbh' } if( $self->{ '_dbh' } );
238             # $self->{_parent}->_load( [ $class ] ) || return( $self->error( $self->{_parent}->error->message ) );
239             # my $o = @_ ? $class->new( $h, @_ ) : $class->new( $h );
240             # return( $self->error( "Unable to instantiate an object of class $class: ", $class->error ) ) if( !defined( $o ) );
241             # return( $o );
242             # }
243              
244             sub _instantiate_object
245             {
246 0     0     my $self = shift( @_ );
247 0           my $name = shift( @_ );
248 0 0 0       return( $self->{ $name } ) if( exists( $self->{ $name } ) && Scalar::Util::blessed( $self->{ $name } ) );
249 0           my $class = shift( @_ );
250             # print( STDERR __PACKAGE__, "::_instantiate_object() called for name '$name' and class '$class'\n" );
251             # $self->message( 3, "called for name '$name' and class '$class'." );
252 0           my $this;
253             my $h =
254             {
255             '_parent' => $self->{_parent},
256             '_field' => $name,
257             '_debug' => $self->{debug},
258 0           };
259 0 0         $h->{_dbh} = $self->{_dbh} if( $self->{_dbh} );
260 0           my $o;
261 0           try
262 0     0     {
263             ## https://stackoverflow.com/questions/32608504/how-to-check-if-perl-module-is-available#comment53081298_32608860
264 0 0         eval( "require $class;" ) unless( defined( *{"${class}::"} ) );
  0            
265             # print( STDERR __PACKAGE__, "::_instantiate_object(): Error while loading module $class? $@\n" );
266             # $self->message( 3, "Error while loading module $class? $@" );
267 0 0         return( $self->error( "Unable to load module $class: $@" ) ) if( $@ );
268 0 0         $o = @_ ? $class->new( $h, @_ ) : $class->new( $h );
269 0 0         return( $self->pass_error( "Unable to instantiate an object of class $class: ", $class->error ) ) if( !defined( $o ) );
270             }
271 0 0         catch( $e )
  0 0          
  0 0          
  0 0          
  0 0          
  0            
  0            
  0            
  0            
  0            
  0            
  0            
  0            
  0            
  0            
  0            
  0            
  0            
  0            
  0            
272 0     0     {
273             # print( STDERR __PACKAGE__, "::_instantiate_object() An error occured while loading module $class for name '$name': $e\n" );
274 0           return( $self->error({ code => 500, message => $e }) );
275 0 0 0       }
  0 0 0        
  0 0          
  0 0          
  0            
  0            
  0            
  0            
276             # $self->message( 3, "Returning newly generated object $o with structure: ", $self->dumper( $o ) );
277 0           return( $o );
278             }
279              
280 0     0     sub _is_boolean { return( 0 ); }
281              
282 0     0     sub _message { return( shift->SUPER::message( @_ ) ); }
283              
284 0     0     sub _messagef { return( shift->SUPER::messagef( @_ ) ); }
285              
286             sub _object_type_to_class
287             {
288 0     0     my $self = shift( @_ );
289 0   0       my $type = shift( @_ ) || return( $self->error( "No object type was provided" ) );
290 0           my $ref = $Net::API::Telegram::TYPE2CLASS;
291 0           $self->_messagef( 3, "\$TYPE2CLASS has %d elements", scalar( keys( %$ref ) ) );
292 0 0         return( $self->error( "No object type '$type' known to get its related class for field $self->{_field}" ) ) if( !exists( $ref->{ $type } ) );
293 0           return( $ref->{ $type } );
294             }
295              
296 0     0     sub _parent { return( shift->_set_get( '_parent', @_ ) ); }
297              
298             sub _set_get_hash
299             {
300 0     0     my $self = shift( @_ );
301 0           my $field = shift( @_ );
302 0           my $class = $field;
303 0           $class =~ tr/-/_/;
304 0           $class =~ s/\_{2,}/_/g;
305 0           $class = join( '', map( ucfirst( lc( $_ ) ), split( /\_/, $class ) ) );
306 0           return( $self->_set_get_hash_as_object( $field, $class, @_ ) );
307             }
308              
309             sub _set_get_number
310             {
311 0     0     my $self = shift( @_ );
312 0           my $field = shift( @_ );
313 0 0         if( @_ )
314             {
315 0           $self->{ $field } = Net::API::Telegram::Number->new( shift( @_ ) );
316             }
317 0           return( $self->{ $field } );
318             }
319              
320             sub _set_get_number_or_object
321             {
322 0     0     my $self = shift( @_ );
323 0           my $field = shift( @_ );
324 0           my $class = shift( @_ );
325 0 0         if( @_ )
326             {
327 0 0 0       if( ref( $_[0] ) eq 'HASH' || Scalar::Util::blessed( $_[0] ) )
328             {
329 0           return( $self->_set_get_object( $field, $class, @_ ) );
330             }
331             else
332             {
333 0           return( $self->_set_get_number( $field, @_ ) );
334             }
335             }
336 0           return( $self->{ $field } );
337             }
338              
339             sub _set_get_object_array2
340             {
341 0     0     my $self = shift( @_ );
342 0           my $field = shift( @_ );
343 0           my $class = shift( @_ );
344 0 0         if( @_ )
345             {
346 0           my $this = shift( @_ );
347 0 0         return( $self->error( "I was expecting an array ref, but instead got '$this'" ) ) if( ref( $this ) ne 'ARRAY' );
348 0           my $arr1 = [];
349 0           foreach my $ref ( @$this )
350             {
351 0 0         return( $self->error( "I was expecting an embeded array ref, but instead got '$ref'." ) ) if( ref( $ref ) ne 'ARRAY' );
352 0           my $arr = [];
353 0           for( my $i = 0; $i < scalar( @$ref ); $i++ )
354             {
355 0           my $o;
356 0 0         if( defined( $ref->[$i] ) )
357             {
358 0 0         return( $self->error( "Parameter provided for adding object of class $class is not a reference." ) ) if( !ref( $ref->[$i] ) );
359 0 0         if( Scalar::Util::blessed( $ref->[$i] ) )
    0          
360             {
361 0           my $pack = $ref->[$i]->isa( $class );
362 0 0         if( $pack )
363             {
364 0           $o->{_parent} = $self->{_parent};
365 0           $o->{_debug} = $self->{_debug};
366 0 0         $o->{_dbh} = $self->{_dbh} if( $self->{_dbh} );
367 0           $o = $ref->[$i];
368             }
369             else
370             {
371 0           return( $self->error( "Object provided ($pack) is not a $class object" ) );
372             }
373             }
374             elsif( ref( $ref->[$i] ) eq 'HASH' )
375             {
376 0           $o = $self->_instantiate_object( $field, $class, $ref->[$i] );
377             }
378             else
379             {
380 0           $self->error( "Warning only: data provided to instaantiate object of class $class is not a hash reference" );
381             }
382             }
383             else
384             {
385 0           $o = $self->_instantiate_object( $field, $class );
386             }
387 0 0         return( $self->error( "Unable to instantiate an object of class $class: ", $class->error ) ) if( !defined( $o ) );
388 0           push( @$arr, $o );
389             }
390 0           push( @$arr1, $arr );
391             }
392 0           $self->{ $field } = $arr1;
393             }
394 0           return( $self->{ $field } );
395             }
396              
397             sub _set_get_object_array
398             {
399 0     0     my $self = shift( @_ );
400 0           my $field = shift( @_ );
401 0           my $class = shift( @_ );
402 0 0         if( @_ )
403             {
404 0           my $ref = shift( @_ );
405 0 0         return( $self->error( "I was expecting an array ref, but instead got '$ref'" ) ) if( ref( $ref ) ne 'ARRAY' );
406 0           my $arr = [];
407 0           for( my $i = 0; $i < scalar( @$ref ); $i++ )
408             {
409 0           $self->_message( 3, "Calling method $class->$field with value '", $ref->[$i], "'" );
410             ## Either the value provided is not defined, and we just instantiate an empty object, or
411             ## the value is a hash and we instantiate a new object with those parameters, or
412             ## we have been provided an existing object
413             ## my $o = defined( $ref->[$i] ) ? $class->new( $h, $ref->[$i] ) : $class->new( $h );
414 0           my $o;
415 0 0         if( defined( $ref->[$i] ) )
416             {
417 0 0         return( $self->error( "Parameter provided for adding object of class $class is not a reference." ) ) if( !ref( $ref->[$i] ) );
418 0 0         if( Scalar::Util::blessed( $ref->[$i] ) )
    0          
419             {
420 0           my $pack = $ref->[$i]->isa( $class );
421 0 0         if( $pack )
422             {
423 0           $o->{_parent} = $self->{_parent};
424 0           $o->{_debug} = $self->{debug};
425 0 0         $o->{_dbh} = $self->{_dbh} if( $self->{_dbh} );
426 0           $o = $ref->[$i];
427             }
428             else
429             {
430 0           return( $self->error( "Object provided ($pack) is not a $class object" ) );
431             }
432             }
433             elsif( ref( $ref->[$i] ) eq 'HASH' )
434             {
435             #$o = $class->new( $h, $ref->[$i] );
436 0           $o = $self->_instantiate_object( $field, $class, $ref->[$i] );
437             }
438             else
439             {
440 0           $self->error( "Warning only: data provided to instaantiate object of class $class is not a hash reference" );
441             }
442             }
443             else
444             {
445 0           $o = $self->_instantiate_object( $field, $class );
446             }
447 0 0         return( $self->error( "Unable to instantiate an object of class $class: ", $class->error ) ) if( !defined( $o ) );
448 0           push( @$arr, $o );
449             }
450 0           $self->{ $field } = $arr;
451             }
452 0           return( $self->{ $field } );
453             }
454              
455             sub _set_get_object_variant
456             {
457 0     0     my $self = shift( @_ );
458 0           my $field = shift( @_ );
459             ## The class precisely depends on what we find looking ahead
460             ## my $class = shift( @_ );
461 0 0         if( @_ )
462             {
463             local $process = sub
464             {
465 0     0     my $ref = shift( @_ );
466 0   0       my $type = $ref->{ 'object' } || return( $self->error( "No object type could be found in hash: ", sub{ $self->_dumper( $ref ) } ) );
467 0           my $class = $self->_object_type_to_class( $type );
468 0           $self->_message( 3, "Object type $type has class $class" );
469 0           my $o = $self->_instantiate_object( $field, $class, $ref );
470 0           $self->{ $field } = $o;
471             ## return( $class->new( %$ref ) );
472             ## return( $self->_set_get_object( 'object', $class, $ref ) );
473 0           };
474            
475 0 0         if( ref( $_[0] ) eq 'HASH' )
    0          
476             {
477 0           my $o = $process->( @_ )
478             }
479             ## AN array of objects hash
480             elsif( ref( $_[0] ) eq 'ARRAY' )
481             {
482 0           my $arr = shift( @_ );
483 0           my $res = [];
484 0           foreach my $data ( @$arr )
485             {
486 0   0       my $o = $process->( $data ) || return( $self->error( "Unable to create object: ", $self->error ) );
487 0           push( @$res, $o );
488             }
489 0           $self->{ $field } = $res;
490             }
491             }
492 0           return( $self->{ $field } );
493             }
494              
495             1;
496              
497             __END__
498