File Coverage

blib/lib/Net/API/Telegram/Generic.pm
Criterion Covered Total %
statement 39 263 14.8
branch 0 110 0.0
condition 0 32 0.0
subroutine 15 37 40.5
pod 1 5 20.0
total 55 447 12.3


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