File Coverage

blib/lib/WWW/Yandex/MailForDomain.pm
Criterion Covered Total %
statement 16 18 88.8
branch n/a
condition n/a
subroutine 6 6 100.0
pod n/a
total 22 24 91.6


line stmt bran cond sub pod time code
1             package WWW::Yandex::MailForDomain;
2             # coding: UTF-8
3              
4 1     1   24651 use strict;
  1         3  
  1         37  
5 1     1   5 use warnings;
  1         1  
  1         25  
6 1     1   971 use utf8;
  1         14  
  1         6  
7              
8             our $VERSION = '0.2';
9              
10 1     1   7125 use LWP::UserAgent;
  1         86895  
  1         42  
11 1     1   11 use URI::Escape;
  1         2  
  1         83  
12 1     1   471 use XML::Simple;
  0            
  0            
13              
14             use Data::Dumper;
15              
16             # ==============================================================================
17              
18             use constant YANDEX_PDD_API_SERVER => 'https://pddimp.yandex.ru/';
19             use constant YANDEX_PDD_API_MAX_ON_PAGE => 100;
20              
21             my %TRANSLATE_TO_PDD = (
22             login => 'login',
23             password => 'password',
24             nickname => 'nickname',
25              
26             enabled => 'enabled',
27             eula_signed => 'signed_eula',
28              
29             first_name => 'iname',
30             last_name => 'fname',
31             date_of_birth => 'birth_date',
32             sex => 'sex',
33              
34             secret_question => 'hintq',
35             secret_answer => 'hinta',
36              
37             #mail_format => 'mail_format',
38             #charset => 'charset',
39             );
40             my %TRANSLATE_FROM_PDD = map { $TRANSLATE_TO_PDD{$_} => $_ } keys(%TRANSLATE_TO_PDD);
41              
42             my %TRANSLATE_SERVER_TO_PDD = (
43             protocol => 'method',
44             host => 'ext_serv',
45             port => 'ext_port',
46             no_ssl => 'isssl',
47             notify => 'callback',
48             );
49              
50             # ==============================================================================
51             # Constructor
52             sub new {
53             my ($class, %config) = @_;
54             %config = () if !(%config);
55              
56             if (! $config{token}) {
57             return undef;
58             }
59             else {
60             my $self = +{};
61             $self = bless $self, ref($class) || $class;
62              
63             $self->_init(\%config);
64             return $self;
65             }
66             }
67             # ------------------------------------------------------------------------------
68             # Set up initial (passed from caller or default) values
69             sub _init
70             {
71             my $self = shift;
72             my ($config) = @_;
73              
74             for (qw(ua token on_error)) {
75             $self->{$_} = $config->{$_};
76             }
77              
78             $self->{_xs} = XML::Simple->new(
79             KeepRoot => 1,
80             );
81              
82             $self->{_errmsg} = '';
83              
84             $self->{_cached_domain} = undef;
85             $self->{_cached_status} = undef;
86             $self->{_cached_total} = undef;
87             $self->{_cached_max_number} = undef;
88             $self->{_cached_found} = undef;
89             }
90             # ------------------------------------------------------------------------------
91             sub _throw_error {
92             my ($self, $msg) = @_;
93              
94             $self->{_errmsg} = $msg;
95             if ($self->{on_error}) {
96             # Fire callback
97             &{$self->{on_error}}($msg);
98             }
99             }
100             # ------------------------------------------------------------------------------
101             sub error {
102             my $self = shift;
103             return $self->{_errmsg} || '';
104             }
105             # ------------------------------------------------------------------------------
106             # Produces full URI for query from sub-uri and server name
107             sub _uri
108             {
109             my ($self, $cmd) = @_;
110             return YANDEX_PDD_API_SERVER . $cmd . '.xml';
111             }
112             # ------------------------------------------------------------------------------
113             # Our User-Agent
114             sub _ua {
115             my $self = shift;
116              
117             if (! defined($self->{ua})) {
118             $self->{ua} = LWP::UserAgent->new(
119             agent => ref($self) . '/' . $VERSION,
120             timeout => 30
121             );
122             $self->{ua}->env_proxy;
123             }
124              
125             return $self->{ua};
126             }
127             # ------------------------------------------------------------------------------
128             # Make request to API
129             sub _query
130             {
131             my ($self, $cmd, %data) = @_;
132             my $r = undef;
133              
134             if (! $self->{token}) {
135             $self->_throw_error('Token is not defined');
136             }
137             else {
138              
139             $data{token} = $self->{token};
140             my $uri = $self->_uri($cmd) . '?' .
141             join('&', map { uri_escape_utf8($_) . "=" . uri_escape_utf8($data{$_}) } keys(%data));
142              
143             my $response = $self->_ua->get($uri);
144              
145             if ($response->is_success) {
146              
147             # Fix wrong encoding in header
148             my $cont = $response->decoded_content;
149             $cont =~ s/windows-1251/UTF-8/;
150              
151             my $xml = $self->{_xs}->XMLin($cont);
152              
153             # Error reported by API
154             if (exists($xml->{page}->{error}->{reason})) {
155             $self->_throw_error('PDD error: ' . $xml->{page}->{error}->{reason});
156             }
157             # All OK, return XML tree
158             else {
159             $r = $xml;
160             }
161             }
162             else {
163             $self->_throw_error('Request failed: ' . $response->status_line);
164             }
165             }
166              
167             return $r;
168             }
169             # ==============================================================================
170             #
171             sub _query_users
172             {
173             my ($self, $on_page, $page_n) = @_;
174              
175             # Something wrong happens, when we set 'page' and 'on_page' parameters,
176             # and number of existing mailboxes is a little.
177              
178             # Therefore, we don't set default values for undefined parameters,
179             # and try to request API without 'page' and 'on_page' fields.
180             # In this case, there is more chances to get a correct list of mailboxes.
181              
182             my %data = ();
183              
184             # Check $page_n
185             if (! defined($page_n)) {
186             #$page_n = 1;
187             }
188             else {
189             $page_n = int($page_n);
190             $page_n = 1 if $page_n < 1;
191             $data{page} = $page_n;
192             }
193              
194             # Check $on_page
195             if (! defined($on_page)) {
196             #$on_page = 1;
197             }
198             else {
199             $on_page = int($on_page);
200             if ($on_page < 1) {
201             $on_page = 1;
202             }
203             elsif ($on_page > YANDEX_PDD_API_MAX_ON_PAGE) {
204             $on_page = YANDEX_PDD_API_MAX_ON_PAGE;
205             }
206             $data{on_page} = $on_page;
207             }
208              
209             my $r = $self->_query('get_domain_users',
210             %data
211             );
212              
213             # Saving some information
214             if (defined($r)) {
215             if ($r->{page}->{domains}->{domain}->{name}) {
216             $self->{_cached_domain} = $r->{page}->{domains}->{domain}->{name};
217             }
218              
219             if (exists($r->{page}->{domains}->{domain}->{status})) {
220             $self->{_cached_status} = $r->{page}->{domains}->{domain}->{status};
221             }
222              
223             if (exists($r->{page}->{domains}->{domain}->{'emails-max-count'})) {
224             $self->{_cached_max_number} = int($r->{page}->{domains}->{domain}->{'emails-max-count'});
225             }
226              
227             if (exists($r->{page}->{domains}->{domain}->{emails}->{total})) {
228             $self->{_cached_total} = int($r->{page}->{domains}->{domain}->{emails}->{total});
229             }
230              
231             if (exists($r->{page}->{domains}->{domain}->{emails}->{found})) {
232             $self->{_cached_found} = int($r->{page}->{domains}->{domain}->{emails}->{found});
233             }
234             }
235              
236             return $r;
237             }
238             # ------------------------------------------------------------------------------
239             #
240             sub refresh_counters
241             {
242             my $self = shift;
243              
244             my $r = $self->_query_users(1, 1);
245             if (defined($r)) {
246             $r = 1;
247             }
248              
249             return $r;
250             }
251             # ------------------------------------------------------------------------------
252             #
253             sub domain
254             {
255             my $self = shift;
256              
257             if (! $self->{_cached_domain}) {
258             $self->refresh_counters;
259             }
260              
261             return $self->{_cached_domain};
262             }
263             # ------------------------------------------------------------------------------
264             #
265             sub domain_status
266             {
267             my $self = shift;
268              
269             if (! $self->{_cached_status}) {
270             $self->refresh_counters;
271             }
272              
273             return $self->{_cached_status};
274             }
275             # ------------------------------------------------------------------------------
276             #
277             sub users_total
278             {
279             my $self = shift;
280              
281             if (! defined($self->{_cached_total})) {
282             $self->refresh_counters;
283             }
284              
285             return $self->{_cached_total};
286             }
287             # ------------------------------------------------------------------------------
288             #
289             sub users_max_number
290             {
291             my $self = shift;
292              
293             if (! defined($self->{_cached_max_number})) {
294             $self->refresh_counters;
295             }
296              
297             return $self->{_cached_max_number};
298             }
299             # ------------------------------------------------------------------------------
300             #
301             sub get_users_one_page
302             {
303             my ($self, $on_page, $page_n) = @_;
304              
305             my $r = $self->_query_users($on_page, $page_n);
306              
307             if (defined($r)) {
308             if ($r->{page}->{domains}->{domain}->{emails}->{email}) {
309             my @boxes = ();
310              
311             my $h = $r->{page}->{domains}->{domain}->{emails}->{email};
312              
313             # I could not tune the XML::Simple to properly parse emails list,
314             # so I applied some untidy logic: get x->{email}->{name} value,
315             # if we have only one item
316              
317             my @found = keys(%$h);
318              
319             if (scalar(@found) == 1) {
320             push(@boxes, $h->{name});
321             }
322             elsif (scalar(@found) > 1) {
323             push(@boxes, @found);
324             }
325              
326             $r = \@boxes;
327             }
328             else {
329             $r = +[];
330             }
331             }
332              
333             return $r;
334             }
335             # ------------------------------------------------------------------------------
336             #
337             sub get_users
338             {
339             my ($self) = @_;
340              
341             # Initially, we try to get list without any parameters
342             my $r = $self->get_users_one_page();
343              
344             if ($r) {
345             # Collect into hash to avoid duplicates. They may occur with
346             # annoying Yandex PDD paging process.
347             my %boxes = ();
348             for (@$r) { $boxes{$_} = 1 }
349              
350             my $found = scalar(keys(%boxes));
351              
352             if ($found < $self->users_total) {
353             # Not complete, needed page by page retrieving
354              
355             my $on_page = YANDEX_PDD_API_MAX_ON_PAGE;
356             my $start_page = 1;
357              
358             if ($found == YANDEX_PDD_API_MAX_ON_PAGE) {
359             # First page is filled wholly
360             $start_page = 2;
361             }
362             else {
363             # First page is not filled, try again from beginning
364             %boxes = ();
365             $start_page = 1;
366             }
367              
368             my $total_pages = int(($self->users_total + YANDEX_PDD_API_MAX_ON_PAGE - 1) / $on_page);
369             my $was_error = 0;
370             for (my $page_n = $start_page; $page_n <= $total_pages; $page_n++) {
371              
372             my $cur = $self->get_users_one_page($on_page, $page_n);
373              
374             if (! defined($cur)) {
375             # Something happens
376             $was_error = 1;
377             last;
378             }
379             else {
380             for (@$cur) { $boxes{$_} = 1 }
381             }
382             }
383              
384             if ($was_error) {
385             $r = undef;
386             }
387             else {
388             my @b = keys(%boxes);
389             $r = \@b;
390             }
391             }
392             else {
393             # Found all by first query
394             my @b = keys(%boxes);
395             $r = \@b;
396             }
397             }
398              
399             return $r;
400             }
401             # ==============================================================================
402             #
403             sub check_user
404             {
405             my ($self, $login) = @_;
406              
407             my $r = $self->_query('check_user',
408             login => $login,
409             );
410              
411             if (defined($r) and defined($r->{page}->{result})) {
412             my $x = $r->{page}->{result} eq 'exists' ? 1 : 0;
413             $r = $x;
414             }
415              
416             return $r;
417             }
418             # ------------------------------------------------------------------------------
419             #
420             sub add_user
421             {
422             my ($self, $login, $password) = @_;
423              
424             my $r = $self->_query('reg_user_token',
425             u_login => $login,
426             u_password => $password,
427             );
428              
429             if (defined($r)) {
430             $r = $r->{page}->{ok}->{uid} || '';
431             $self->refresh_counters; # Don't care about retval
432             }
433              
434             return $r;
435             }
436             # ------------------------------------------------------------------------------
437             #
438             sub add_user_encrypted
439             {
440             my ($self, $login, $password) = @_;
441              
442             my $r = $self->_query('reg_user_crypto',
443             login => $login,
444             password => $password,
445             );
446              
447             if (defined($r)) {
448             $r = $r->{page}->{ok}->{uid} || '';
449             $self->refresh_counters; # Don't care about retval
450             }
451              
452             return $r;
453             }
454             # ------------------------------------------------------------------------------
455             #
456             sub delete_user
457             {
458             my ($self, $login) = @_;
459              
460             my $r = $self->_query('delete_user',
461             login => $login,
462             );
463              
464             if (defined($r)) {
465             $r = exists($r->{page}->{ok}) ? 1 : undef;
466             $self->refresh_counters; # Don't care about retval
467             }
468              
469             return $r;
470             }
471             # ------------------------------------------------------------------------------
472             #
473             sub change_password
474             {
475             my ($self, $login, $new_password) = @_;
476              
477             my $r = $self->_query('edit_user',
478             login => $login,
479             password => $new_password,
480             );
481              
482             if (defined($r)) {
483             $r = $r->{page}->{ok}->{uid} || '';
484             }
485              
486             return $r;
487             }
488             # ------------------------------------------------------------------------------
489             #
490             sub modify_user
491             {
492             my ($self, $login, %data) = @_;
493              
494             my %q = ();
495             for (qw(first_name last_name sex secret_question secret_answer)) {
496             $q{$_} = $data{$_} if defined($data{$_});
497             }
498              
499             # Field 'sex' must be integer in [0..2] interval
500             if (exists($q{sex})) {
501             $q{sex} =~ s/\D//g;
502             $q{sex} = int($q{sex});
503             if (($q{sex} < 0) or ($q{sex} > 2)) {
504             delete $q{sex};
505             }
506             }
507              
508             # Translate field names
509             my %qt = ();
510             for (keys(%q)) {
511             $qt{$TRANSLATE_TO_PDD{$_}} = $q{$_};
512             }
513              
514             print Dumper(\%qt);
515              
516             # Execute query
517             my $r = $self->_query('edit_user',
518             login => $login,
519             %qt
520             );
521              
522             if (defined($r)) {
523             $r = $r->{page}->{ok}->{uid} || '';
524             }
525              
526             return $r;
527             }
528             # ------------------------------------------------------------------------------
529             #
530             sub get_user_info
531             {
532             my ($self, $login, %data) = @_;
533              
534             my $r = $self->_query('get_user_info',
535             login => $login,
536             );
537              
538             if (defined($r) and defined($r->{page}->{domain}->{user})) {
539              
540             print Dumper($r), "\n\n";
541              
542             my $info = $r->{page}->{domain}->{user};
543             my $u = +{};
544              
545             for my $k (keys(%$info)) {
546             if (exists($TRANSLATE_FROM_PDD{$k})) {
547             my $key = $TRANSLATE_FROM_PDD{$k};
548             my $val = ref($info->{$k}) eq 'HASH' ? undef : $info->{$k};
549             $u->{$key} = $val;
550             }
551             }
552              
553             $r = $u;
554             }
555              
556             return $r;
557             }
558             # ------------------------------------------------------------------------------
559             #
560             sub get_unread_count
561             {
562             my ($self, $login) = @_;
563              
564             my $r = $self->_query('get_mail_info',
565             login => $login,
566             );
567              
568             if (defined($r)) {
569             $r = $r->{page}->{ok}->{new_messages} || 0;
570             }
571              
572             return $r;
573             }
574             # ==============================================================================
575             #
576             sub register_source
577             {
578             my ($self, %opt) = @_;
579             my $r = undef;
580              
581             if ($opt{host}) {
582              
583             $opt{protocol} = ($opt{protocol} && (lc($opt{protocol}) eq 'imap')) ? 'imap' : 'pop3';
584             $opt{port} = int($opt{port}) if defined $opt{port};
585             $opt{no_ssl} = $opt{no_ssl} ? 'no' : undef;
586              
587             my %q = ();
588             for (keys(%opt)) {
589             $q{$TRANSLATE_SERVER_TO_PDD{$_}} = $opt{$_} if defined($opt{$_});
590             }
591              
592             $r = $self->_query('set_domain', %q);
593              
594             if (defined($r)) {
595             $r = exists($r->{page}->{ok}) ? 1 : undef;
596             }
597             }
598             else {
599             $self->_throw_error('Remote host is not defined');
600             }
601              
602             return $r;
603             }
604             # ------------------------------------------------------------------------------
605             #
606             sub start_import
607             {
608             my ($self, $login, %data) = @_;
609              
610             my %q = ();
611              
612             $q{login} = $login;
613             $q{ext_login} = $data{remote_login} if defined $data{remote_login};
614             $q{password} = $data{remote_password} if defined $data{remote_password};
615              
616             my $r = $self->_query('start_import', %q);
617              
618             if (defined($r)) {
619             $r = exists($r->{page}->{ok}) ? 1 : undef;
620             }
621              
622             return $r;
623             }
624             # ------------------------------------------------------------------------------
625             #
626             sub stop_import
627             {
628             my ($self, $login) = @_;
629              
630             my $r = $self->_query('stop_import',
631             login => $login,
632             );
633              
634             if (defined($r)) {
635             $r = exists($r->{page}->{ok}) ? 1 : undef;
636             }
637              
638             return $r;
639             }
640             # ------------------------------------------------------------------------------
641             #
642             sub check_import_status
643             {
644             my ($self, $login) = @_;
645              
646             my $r = $self->_query('check_import',
647             login => $login,
648             );
649              
650             if (defined($r)) {
651             my $u = +{};
652             $u->{time} = $r->{page}->{ok}->{last_check};
653             $u->{state} = $r->{page}->{ok}->{state};
654             $r = $u;
655             }
656              
657             return $r;
658             }
659             # ==============================================================================
660             #
661             sub set_forwarding
662             {
663             my ($self, $from, $to, $dont_keep) = @_;
664              
665             my $copy = $dont_keep ? 'no' : 'yes';
666              
667             my $r = $self->_query('set_forward',
668             login => $from,
669             address => $to,
670             copy => $copy,
671             );
672              
673             if (defined($r)) {
674             $r = exists($r->{page}->{ok}) ? 1 : undef;
675             }
676              
677             return $r;
678             }
679             # ==============================================================================
680             1;
681             __END__