File Coverage

blib/lib/Net/API/Telegram/Generic.pm
Criterion Covered Total %
statement 34 249 13.6
branch 0 110 0.0
condition 0 32 0.0
subroutine 12 34 35.2
pod 3 5 60.0
total 49 430 11.4


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