File Coverage

blib/lib/Geonode/Free/ProxyList.pm
Criterion Covered Total %
statement 136 163 83.4
branch 40 42 95.2
condition 40 42 95.2
subroutine 27 34 79.4
pod 20 20 100.0
total 263 301 87.3


line stmt bran cond sub pod time code
1             package Geonode::Free::ProxyList;
2              
3 2     2   19820 use 5.010;
  2         16  
4 2     2   10 use strict;
  2         4  
  2         45  
5 2     2   10 use warnings;
  2         4  
  2         59  
6 2     2   11 use Carp 'croak';
  2         4  
  2         163  
7 2     2   13 use List::Util qw( shuffle );
  2         4  
  2         695  
8 2     2   1164 use List::MoreUtils qw( uniq );
  2         27741  
  2         13  
9 2     2   3685 use LWP::UserAgent;
  2         91072  
  2         92  
10 2     2   1611 use JSON::PP;
  2         29229  
  2         182  
11 2     2   779 use utf8;
  2         20  
  2         12  
12              
13 2     2   1014 use Geonode::Free::Proxy;
  2         7  
  2         4695  
14              
15             =head1 NAME
16              
17             Geonode::Free::ProxyList - Get Free Geonode Proxies by using some filters
18              
19             =head1 VERSION
20              
21             Version 0.0.5
22              
23             =cut
24              
25             our $VERSION = '0.0.5';
26              
27             my $API_ROOT = 'https://proxylist.geonode.com/api/proxy-list?';
28              
29             =head1 SYNOPSIS
30              
31             Get Geonode's free proxy list and apply some filters. You can later choose them by random.
32              
33             my $proxy_list = Geonode::Free::ProxyList->new();
34              
35             $list->set_filter_google('true');
36             $list->set_filter_port(3128);
37             $list->set_filter_limit(200);
38              
39             $list->add_proxies; # Add proxies to the list for current filters
40              
41             $list->set_filter_google('false');
42             $list->set_filter_port(); # reset filter
43             $list->set_filter_limit(); # reset filter
44             $list->set_filter_protocol_list( [ 'socks4', 'socks5' ] );
45             $list->set_filter_speed('fast');
46              
47             $list->add_proxies; # Add proxies to the list for current filters
48              
49             # List of proxies is shuffled
50              
51             my $some_proxy = $list->get_next; # Repeats when list is exhausted
52             my $other_proxy = $list->get_next; # Repeats when list is exhausted
53              
54             my $random_proxy = $list->get_random_proxy; # Can repeat
55              
56             $some_proxy->get_methods(); # [ 'http', 'socks5' ]
57              
58             Geonode::Free::Proxy::prefer_socks(); # Will use socks for url, if available
59              
60             $some_proxy->get_url(); # 'socks://127.0.0.1:3128';
61              
62             Geonode::Free::Proxy::prefer_http(); # Will use http url, if available
63              
64             $some_proxy->get_url(); # 'http://127.0.0.1:3128';
65              
66             $some_proxy->can_use_http(); # 1
67             $some_proxy->can_use_socks(); # 1
68              
69             $other_proxy->can_use_socks(); # q()
70             $other_proxy->can_use_http(); # 1
71              
72             Geonode::Free::Proxy::prefer_socks(); # Will use socks for url, if available
73              
74             $some_proxy->get_url(); # 'http://foo.bar.proxy:1234';
75              
76             =head1 SUBROUTINES/METHODS
77              
78             =head2 new
79              
80             Instantiate Geonode::Free::ProxyList object
81              
82             =cut
83              
84             sub new {
85 1     1 1 89 my $self = bless {
86             proxy_list => [],
87             index => 0,
88             filters => {},
89             ua => LWP::UserAgent->new()
90             },
91             shift;
92              
93 1         2873 $self->reset_filters();
94              
95 1         3 return $self;
96             }
97              
98             =head2 reset_proxy_list
99              
100             Clears proxy list
101              
102             =cut
103              
104             sub reset_proxy_list {
105 0     0 1 0 my $self = @_;
106              
107 0         0 $self->{proxy_list} = [];
108              
109 0         0 return;
110             }
111              
112             =head2 reset_filters
113              
114             Reset filtering options
115              
116             =cut
117              
118             sub reset_filters {
119 1     1 1 3 my ($self) = @_;
120              
121             $self->{filters} = {
122 1         11 country => undef,
123             google => undef,
124             filterPort => undef,
125             protocols => undef,
126             anonymityLevel => undef,
127             speed => undef,
128             filterByOrg => undef,
129             filterUpTime => undef,
130             filterLastChecked => undef,
131             limit => undef
132             };
133              
134 1         2 return;
135             }
136              
137             =head2 set_filter_country
138              
139             Set country filter. Requires a two character uppercase string or undef to reset the filter
140              
141             =cut
142              
143             sub set_filter_country {
144 9     9 1 4717 my ( $self, $country ) = @_;
145              
146 9 100 100     66 if ( defined $country && $country !~ m{^[A-Z]{2}$}sxm ) {
147 6         71 croak q()
148             . "ERROR: '$country' is not a two character uppercase code\n"
149             . "Please, check valid values at following url:\n"
150             . 'https://geonode.com/free-proxy-list';
151             }
152              
153 3         8 $self->{filters}{country} = $country;
154              
155 3         10 return;
156             }
157              
158             =head2 set_filter_google
159              
160             Set google filter. Allowed values are 'true'/'false'. You can use undef to reset the filter
161              
162             =cut
163              
164             sub set_filter_google {
165 6     6 1 2825 my ( $self, $google ) = @_;
166              
167 6 100 100     46 if ( defined $google && $google !~ m{^(?: true|false )$}sxm ) {
168 2         23 croak q()
169             . "ERROR: '$google' is not a valid value for google filter\n"
170             . 'Valid values are: true/false';
171             }
172              
173 4         12 $self->{filters}{google} = $google;
174              
175 4         8 return;
176             }
177              
178             =head2 set_filter_port
179              
180             Set port filter. Allowed values are numbers that does not start by zero. You can use undef to reset the filter
181              
182             =cut
183              
184             sub set_filter_port {
185 7     7 1 3609 my ( $self, $port ) = @_;
186              
187 7 100 100     54 if ( defined $port && $port !~ m{^(?: (?!0)[0-9]++ )$}sxm ) {
188 4         37 croak "ERROR: '$port' is not a valid value for por filter";
189             }
190              
191 3         8 $self->{filters}{filterPort} = $port;
192              
193 3         7 return;
194             }
195              
196             =head2 set_filter_protocol_list
197              
198             Set protocol list filter. Allowed values are http, https, socks4, socks5. You can use an scalar or a list of values. By using undef you can reset the filter
199              
200             =cut
201              
202             sub set_filter_protocol_list {
203 9     9 1 4063 my ( $self, $protocol_list ) = @_;
204              
205 9 100 100     68 if ( defined $protocol_list && ref $protocol_list eq q() ) {
    100 100        
206 2         8 $protocol_list = [$protocol_list];
207             }
208             elsif ( defined $protocol_list && ref $protocol_list ne 'ARRAY' ) {
209 1         10 croak 'ERROR: just a single scalar or an array reference are accepted';
210             }
211              
212 8 100       21 if ( !defined $protocol_list ) {
213 1         4 $self->{filters}{protocols} = undef;
214 1         2 return;
215             }
216              
217 7         13 my @list;
218 7         12 for my $option ( @{$protocol_list} ) {
  7         19  
219 16 100       66 if ( $option !~ m{ ^(?:https?|socks[45])$ }sxm ) {
220 1         12 croak "ERROR: '$option' is not a valid value for protocol list";
221             }
222              
223 15         34 push @list, $option;
224             }
225              
226 6 100 66     28 if ( defined $protocol_list && @list == 0 ) {
227 1         11 croak 'ERROR: Cannot set empty protocol list';
228             }
229              
230 5         38 $self->{filters}{protocols} = [ uniq @list ];
231              
232 5         18 return;
233             }
234              
235             =head2 set_filter_anonymity_list
236              
237             Set anonimity list filter. Allowed values are http, https, socks4, socks5. You can use an scalar or a list of values. By using undef you can reset the filter
238              
239             =cut
240              
241             sub set_filter_anonymity_list {
242 9     9 1 4095 my ( $self, $anonymity_list ) = @_;
243              
244 9 100 100     66 if ( defined $anonymity_list && ref $anonymity_list eq q() ) {
    100 100        
245 2         6 $anonymity_list = [$anonymity_list];
246             }
247             elsif ( defined $anonymity_list && ref $anonymity_list ne 'ARRAY' ) {
248 1         10 croak 'ERROR: just a single scalar or an array reference are accepted';
249             }
250              
251 8 100       24 if ( !defined $anonymity_list ) {
252 1         3 $self->{filters}{anonymityLevel} = undef;
253 1         3 return;
254             }
255              
256 7         12 my @list;
257 7         12 for my $option ( @{$anonymity_list} ) {
  7         17  
258 14 100       57 if ( $option !~ m{ ^(?:elite|anonymous|transparent)$ }sxm ) {
259 1         12 croak "ERROR: '$option' is not a valid value for anonymity list";
260             }
261              
262 13         32 push @list, $option;
263             }
264              
265 6 100 66     25 if ( defined $anonymity_list && @list == 0 ) {
266 1         9 croak 'ERROR: Cannot set empty protocol list';
267             }
268              
269 5         34 $self->{filters}{anonymityLevel} = [ uniq @list ];
270              
271 5         18 return;
272             }
273              
274             =head2 set_filter_speed
275              
276             Set speed filter. Allowed values are: fast, medium, slow. You can use undef to reset the filter
277              
278             =cut
279              
280             sub set_filter_speed {
281 6     6 1 2623 my ( $self, $speed ) = @_;
282              
283 6 100 100     47 if ( defined $speed && $speed !~ m{^(?: fast|medium|slow )$}sxm ) {
284 1         14 croak q()
285             . "ERROR: '$speed' is not a valid value for por speed\n"
286             . 'Valid values are: fast/slow/medium';
287             }
288              
289 5         14 $self->{filters}{speed} = $speed;
290              
291 5         11 return;
292             }
293              
294             =head2 set_filter_org
295              
296             Set organization filter. Requires some non empty string. You can use undef to reset the filter
297              
298             =cut
299              
300             sub set_filter_org {
301 3     3 1 1621 my ( $self, $org ) = @_;
302              
303 3 100 100     20 if ( defined $org && $org eq q() ) {
304 1         10 croak 'ERROR: Cannot set empty organization filter';
305             }
306              
307 2         5 $self->{filters}{filterByOrg} = $org;
308              
309 2         5 return;
310             }
311              
312             =head2 set_filter_uptime
313              
314             Set uptime filter. Allowed values are: 0-100 in 10% increments. You can use undef to reset the filter
315              
316             =cut
317              
318             sub set_filter_uptime {
319 106     106 1 70721 my ( $self, $uptime ) = @_;
320              
321 106 100 100     776 if ( defined $uptime && $uptime !~ m{^(?: 0 | [1-9]0 | 100 )$}sxm ) {
322 94         887 croak q()
323             . "ERROR: '$uptime' is not a valid value for por uptime\n"
324             . 'Valid values are: 0-100% in 10% increments';
325             }
326              
327 12         49 $self->{filters}{filterUpTime} = $uptime;
328              
329 12         27 return;
330             }
331              
332             =head2 set_filter_last_checked
333              
334             Set last checked filter. Allowed values are: 1-9 and 20-60 in 10% increments. You can use undef to reset the filter
335              
336             =cut
337              
338             sub set_filter_last_checked {
339 75     75 1 49214 my ( $self, $last_checked ) = @_;
340              
341 75 100 100     536 if ( defined $last_checked && $last_checked !~ m{^(?:[1-9]|[1-6]0)$}sxm ) {
342 59         590 croak q()
343             . "ERROR: '$last_checked' is not a valid value for por uptime\n"
344             . 'Valid values are: 0-100% in 10% increments';
345             }
346              
347 16         43 $self->{filters}{filterLastChecked} = $last_checked;
348              
349 16         36 return;
350             }
351              
352             =head2 set_filter_limit
353              
354             Set speed filter. Allowed values are numbers greater than 0. You can use undef to reset the filter
355              
356             =cut
357              
358             sub set_filter_limit {
359 6     6 1 3639 my ( $self, $limit ) = @_;
360              
361 6 100 100     46 if ( defined $limit && $limit !~ m{^ (?!0)[0-9]++ $}sxm ) {
362 4         44 croak q()
363             . "ERROR: '$limit' is not a valid value for por speed\n"
364             . 'Valid values are: numbers > 0';
365             }
366              
367 2         8 $self->{filters}{limit} = $limit;
368              
369 2         6 return;
370             }
371              
372             =head2 set_env_proxy
373              
374             Use proxy based on environment variables
375              
376             See: https://metacpan.org/pod/LWP::UserAgent#env_proxy
377              
378             Example:
379              
380             $proxy_list->set_env_proxy();
381              
382             =cut
383              
384             sub set_env_proxy {
385 0     0 1 0 my ($self) = @_;
386              
387 0         0 $self->{ua}->env_proxy;
388              
389 0         0 return;
390             }
391              
392             =head2 set_proxy
393              
394             Exposes LWP::UserAgent's proxy method to configure proxy server
395              
396             See: https://metacpan.org/pod/LWP::UserAgent#proxy
397              
398             Example:
399              
400             $proxy_list->proxy(['http', 'ftp'], 'http://proxy.sn.no:8001/');
401              
402             =cut
403              
404             sub set_proxy {
405 0     0 1 0 my ( $self, @params ) = @_;
406              
407 0         0 $self->{ua}->proxy(@params);
408              
409 0         0 return;
410             }
411              
412             =head2 set_timeout
413              
414             Set petition timeout. Exposes LWP::UserAgent's timeout method
415              
416             See: https://metacpan.org/pod/LWP::UserAgent#timeout
417              
418             Example:
419              
420             $proxy_list->timeout(10);
421              
422             =cut
423              
424             sub set_timeout {
425 0     0 1 0 my ( $self, @params ) = @_;
426              
427 0         0 $self->{ua}->timeout(@params);
428              
429 0         0 return;
430             }
431              
432             =head2 add_proxies
433              
434             Add proxy list according to stored filters
435              
436             =cut
437              
438             sub add_proxies {
439 0     0 1 0 my ($self) = @_;
440              
441 0         0 my $response = $self->{ua}->get( $API_ROOT . $self->_calculate_api_url );
442              
443 0 0       0 if ( !$response->is_success ) {
444 0         0 croak 'ERROR: Could not get url, ' . $response->status_line;
445             }
446              
447 0     0   0 my $data = encode( 'utf-8', $response->decoded_content, sub { q() } );
  0         0  
448              
449 0         0 $self->{proxy_list} = [ shuffle @{ $self->_create_proxy_list($data) } ];
  0         0  
450 0         0 $self->{index} = 0;
451              
452 0         0 return;
453             }
454              
455             sub _create_proxy_list {
456 1     1   236 my ( $self, $struct ) = @_;
457              
458 1         8 $struct = decode_json $struct;
459              
460 1         43383 my %proxies = map { $_->id => $_ } $self->get_all_proxies;
  0         0  
461              
462 1         3 for my $item ( @{ $struct->{data} } ) {
  1         4  
463             $proxies{ $item->{_id} } = Geonode::Free::Proxy->new(
464             $item->{_id},
465             $item->{ip},
466             $item->{port},
467             $item->{protocols}
468 10         40 );
469             }
470              
471 1         26 return [ values %proxies ];
472             }
473              
474             sub _calculate_api_url {
475 9     9   700 my $self = shift;
476              
477             return join q(&),
478 30         60 map { $self->_serialize_filter($_) }
479 90         170 grep { defined $self->{filters}{$_} }
480 9         15 sort keys %{ $self->{filters} };
  9         66  
481             }
482              
483             sub _serialize_filter {
484 30     30   50 my ( $self, $filter ) = @_;
485              
486 30         48 my $value = $self->{filters}{$filter};
487              
488             return ref $value eq 'ARRAY'
489 30 100       119 ? join q(&), map { "$filter=$_" } sort @{ $value }
  14         63  
  8         18  
490             : $filter . q(=) . $value;
491             }
492              
493             =head2 get_all_proxies
494              
495             Return the whole proxy list
496              
497             =cut
498              
499             sub get_all_proxies {
500 1     1 1 3 my ($self) = @_;
501              
502 1         2 return @{ $self->{proxy_list} };
  1         5  
503             }
504              
505             =head2 get_random_proxy
506              
507             Returns a proxy from the list at random (with repetition)
508              
509             =cut
510              
511             sub get_random_proxy {
512 0     0 1 0 my ($self) = @_;
513              
514 0         0 my $rand_index = int rand @{ $self->{proxy_list} };
  0         0  
515              
516 0         0 return $self->{proxy_list}[$rand_index];
517             }
518              
519             =head2 get_next
520              
521             Returns next proxy from the shuffled list (no repetition until list is exhausted)
522              
523             =cut
524              
525             sub get_next {
526 10     10 1 9341 my ($self) = @_;
527              
528 10         21 my $proxy = $self->{proxy_list}[ $self->{index} ];
529              
530 10         21 $self->{index} = $self->{index} + 1;
531              
532 10 100       16 if ( $self->{index} > @{ $self->{proxy_list} } - 1 ) {
  10         32  
533 1         2 $self->{index} = 0;
534             }
535              
536 10         25 return $proxy;
537             }
538              
539             =head1 AUTHOR
540              
541             Julio de Castro, C<< >>
542              
543             =head1 BUGS
544              
545             Please report any bugs or feature requests to C, or through
546             the web interface at L.
547              
548             I will be notified, and then you'll automatically be notified of progress on your bug as I make changes.
549              
550             =head1 SUPPORT
551              
552             You can find documentation for this module with the perldoc command.
553              
554             perldoc Geonode::Free::ProxyList
555              
556             You can also look for information at:
557              
558             =over 4
559              
560             =item * RT: CPAN's request tracker (report bugs here)
561              
562             L
563              
564             =item * CPAN Ratings
565              
566             L
567              
568             =item * Search CPAN
569              
570             L
571              
572             =back
573              
574              
575             =head1 ACKNOWLEDGEMENTS
576              
577              
578             =head1 LICENSE AND COPYRIGHT
579              
580             This software is Copyright (c) 2021 by Julio de Castro.
581              
582             This is free software, licensed under:
583              
584             The Artistic License 2.0 (GPL Compatible)
585              
586              
587             =cut
588              
589             1; # End of Geonode::Free::ProxyList