fix typos in DXUser.pm
[spider.git] / perl / DXUser.pm
1 #
2 # DX cluster user routines
3 #
4 # Copyright (c) 1998 - Dirk Koopman G1TLH
5 #
6 #
7 #
8
9 package DXUser;
10
11 use DXLog;
12 use DB_File;
13 use Data::Dumper;
14 use Fcntl;
15 use IO::File;
16 use DXUtil;
17 use LRU;
18 use File::Copy;
19 use JSON;
20 use DXDebug;
21
22 use strict;
23
24 use vars qw(%u $dbm $filename %valid $lastoperinterval $lasttime $lru $lrusize $tooold $v3 $v4);
25
26 %u = ();
27 $dbm = undef;
28 $filename = undef;
29 $lastoperinterval = 60*24*60*60;
30 $lasttime = 0;
31 $lrusize = 2000;
32 $tooold = 86400 * 365;          # this marks an old user who hasn't given enough info to be useful
33 $v3 = 0;
34 $v4 = 0;
35 my $json;
36
37 our $maxconnlist = 3;                   # remember this many connection time (duration) [start, end] pairs
38
39 # hash of valid elements and a simple prompt
40 %valid = (
41                   call => '0,Callsign',
42                   alias => '0,Real Callsign',
43                   name => '0,Name',
44                   qth => '0,Home QTH',
45                   lat => '0,Latitude,slat',
46                   long => '0,Longitude,slong',
47                   qra => '0,Locator',
48                   email => '0,E-mail Address,parray',
49                   priv => '9,Privilege Level',
50                   lastin => '0,Last Time in,cldatetime',
51                   passwd => '9,Password,yesno',
52                   passphrase => '9,Pass Phrase,yesno',
53                   addr => '0,Full Address',
54                   'sort' => '0,Type of User', # A - ak1a, U - User, S - spider cluster, B - BBS
55                   xpert => '0,Expert Status,yesno',
56                   bbs => '0,Home BBS',
57                   node => '0,Last Node',
58                   homenode => '0,Home Node',
59                   lockout => '9,Locked out?,yesno',     # won't let them in at all
60                   dxok => '9,Accept DX Spots?,yesno', # accept his dx spots?
61                   annok => '9,Accept Announces?,yesno', # accept his announces?
62                   lang => '0,Language',
63                   hmsgno => '0,Highest Msgno',
64                   group => '0,Group,parray',    # used to create a group of users/nodes for some purpose or other
65                   buddies => '0,Buddies,parray',
66                   isolate => '9,Isolate network,yesno',
67                   wantbeep => '0,Req Beep,yesno',
68                   wantann => '0,Req Announce,yesno',
69                   wantwwv => '0,Req WWV,yesno',
70                   wantwcy => '0,Req WCY,yesno',
71                   wantecho => '0,Req Echo,yesno',
72                   wanttalk => '0,Req Talk,yesno',
73                   wantwx => '0,Req WX,yesno',
74                   wantdx => '0,Req DX Spots,yesno',
75                   wantemail => '0,Req Msgs as Email,yesno',
76                   pagelth => '0,Current Pagelth',
77                   pingint => '9,Node Ping interval',
78                   nopings => '9,Ping Obs Count',
79                   wantlogininfo => '0,Login Info Req,yesno',
80           wantgrid => '0,Show DX Grid,yesno',
81                   wantann_talk => '0,Talklike Anns,yesno',
82                   wantpc16 => '9,Want Users from node,yesno',
83                   wantsendpc16 => '9,Send PC16,yesno',
84                   wantroutepc19 => '9,Route PC19,yesno',
85                   wantusstate => '0,Show US State,yesno',
86                   wantdxcq => '0,Show CQ Zone,yesno',
87                   wantdxitu => '0,Show ITU Zone,yesno',
88                   wantgtk => '0,Want GTK interface,yesno',
89                   wantpc9x => '0,Want PC9X interface,yesno',
90                   lastoper => '9,Last for/oper,cldatetime',
91                   nothere => '0,Not Here Text',
92                   registered => '9,Registered?,yesno',
93                   prompt => '0,Required Prompt',
94                   version => '1,Version',
95                   build => '1,Build',
96                   believe => '1,Believable nodes,parray',
97                   lastping => '1,Last Ping at,ptimelist',
98                   maxconnect => '1,Max Connections',
99                   startt => '0,Start Time,cldatetime',
100                   connlist => '1,Connections,parraydifft',
101                  );
102
103 #no strict;
104 sub AUTOLOAD
105 {
106         no strict;
107         my $name = $AUTOLOAD;
108   
109         return if $name =~ /::DESTROY$/;
110         $name =~ s/^.*:://o;
111   
112         confess "Non-existant field '$AUTOLOAD'" if !$valid{$name};
113         # this clever line of code creates a subroutine which takes over from autoload
114         # from OO Perl - Conway
115         *$AUTOLOAD = sub {@_ > 1 ? $_[0]->{$name} = $_[1] : $_[0]->{$name}};
116        goto &$AUTOLOAD;
117 }
118
119 #use strict;
120
121 #
122 # initialise the system
123 #
124 sub init
125 {
126         my $mode = shift;
127   
128         my $ufn;
129         my $convert;
130         
131         my $fn = "users";
132
133         eval {
134                 require Storable;
135         };
136
137         if ($@) {
138                 $ufn = localdata("users.v2");
139                 $v3 = $convert = 0;
140                 dbg("the module Storable appears to be missing!!");
141                 dbg("trying to continue in compatibility mode (this may fail)");
142                 dbg("please install Storable from CPAN as soon as possible");
143         } else {
144                 import Storable qw(nfreeze thaw);
145
146                 $ufn = localdata("users.v3");
147                 $v3 = 1;
148                 $convert++ if -e localdata("users.v2") && !-e $ufn;
149         }
150         
151         if ($mode) {
152                 $dbm = tie (%u, 'DB_File', $ufn, O_CREAT|O_RDWR, 0666, $DB_BTREE) or confess "can't open user file: $fn ($!) [rebuild it from user_asc?]";
153         } else {
154                 $dbm = tie (%u, 'DB_File', $ufn, O_RDONLY, 0666, $DB_BTREE) or confess "can't open user file: $fn ($!) [rebuild it from user_asc?]";
155         }
156
157         die "Cannot open $ufn ($!)\n" unless $dbm;
158
159         $lru = LRU->newbase("DXUser", $lrusize);
160         
161         # do a conversion if required
162         if ($dbm && $convert) {
163                 my ($key, $val, $action, $count, $err) = ('','',0,0,0);
164                 
165                 my %oldu;
166                 dbg("Converting the User File to V$convert ");
167                 dbg("This will take a while, I suggest you go and have cup of strong tea");
168                 my $odbm = tie (%oldu, 'DB_File', localdata("users.v2"), O_RDONLY, 0666, $DB_BTREE) or confess "can't open user file: $fn.v2 ($!) [rebuild it from user_asc?]";
169         for ($action = R_FIRST; !$odbm->seq($key, $val, $action); $action = R_NEXT) {
170                         my $ref;
171                         eval { $ref = asc_decode($val) };
172                         unless ($@) {
173                                 if ($ref) {
174                                         $ref->put;
175                                         $count++;
176                                 } else {
177                                         $err++
178                                 }
179                         } else {
180                                 Log('err', "DXUser: error decoding $@");
181                         }
182                 } 
183                 undef $odbm;
184                 untie %oldu;
185                 dbg("Conversion completed $count records $err errors");
186         }
187         $filename = $ufn;
188 }
189
190 sub del_file
191 {
192         # with extreme prejudice
193         unlink "$main::data/users.v3";
194         unlink "$main::local_data/users.v3";
195 }
196
197 #
198 # periodic processing
199 #
200 sub process
201 {
202         if ($main::systime > $lasttime + 15) {
203                 $dbm->sync if $dbm;
204                 $lasttime = $main::systime;
205         }
206 }
207
208 #
209 # close the system
210 #
211
212 sub finish
213 {
214         undef $dbm;
215         untie %u;
216 }
217
218 #
219 # new - create a new user
220 #
221
222 sub alloc
223 {
224         my $pkg = shift;
225         my $call = uc shift;
226         my $self = bless {call => $call, 'sort'=>'U'}, $pkg;
227         return $self;
228 }
229
230 sub new
231 {
232         my $pkg = shift;
233         my $call = shift;
234         #  $call =~ s/-\d+$//o;
235   
236 #       confess "can't create existing call $call in User\n!" if $u{$call};
237
238         my $self = $pkg->alloc($call);
239         $self->put;
240         return $self;
241 }
242
243 #
244 # get - get an existing user - this seems to return a different reference everytime it is
245 #       called - see below
246 #
247
248 sub get
249 {
250         my $call = uc shift;
251         my $data;
252         
253         # is it in the LRU cache?
254         my $ref = $lru->get($call);
255         return $ref if $ref && ref $ref eq 'DXUser';
256         
257         # search for it
258         unless ($dbm->get($call, $data)) {
259                 eval { $ref = decode($data); };
260                 
261                 if ($ref) {
262                         if (!UNIVERSAL::isa($ref, 'DXUser')) {
263                                 dbg("DXUser::get: got strange answer from decode of $call". ref $ref. " ignoring");
264                                 return undef;
265                         }
266                         # we have a reference and it *is* a DXUser
267                 } else {
268                         if ($@) {
269                                 LogDbg('err', "DXUser::get decode error on $call '$@'");
270                         } else {
271                                 dbg("DXUser::get: no reference returned from decode of $call $!");
272                         }
273                         return undef;
274                 }
275                 $lru->put($call, $ref);
276                 return $ref;
277         }
278         return undef;
279 }
280
281 #
282 # get an existing either from the channel (if there is one) or from the database
283 #
284 # It is important to note that if you have done a get (for the channel say) and you
285 # want access or modify that you must use this call (and you must NOT use get's all
286 # over the place willy nilly!)
287 #
288
289 sub get_current
290 {
291         my $call = uc shift;
292   
293         my $dxchan = DXChannel::get($call);
294         if ($dxchan) {
295                 my $ref = $dxchan->user;
296                 return $ref if $ref && UNIVERSAL::isa($ref, 'DXUser');
297
298                 dbg("DXUser::get_current: got invalid user ref for $call from dxchan $dxchan->{call} ". ref $ref. " ignoring");
299         }
300         return get($call);
301 }
302
303 #
304 # get all callsigns in the database 
305 #
306
307 sub get_all_calls
308 {
309         return (sort keys %u);
310 }
311
312 #
313 # put - put a user
314 #
315
316 sub put
317 {
318         my $self = shift;
319         confess "Trying to put nothing!" unless $self && ref $self;
320         my $call = $self->{call};
321
322         $dbm->del($call);
323         delete $self->{annok} if $self->{annok};
324         delete $self->{dxok} if $self->{dxok};
325
326         $lru->put($call, $self);
327         my $ref = $self->encode;
328         $dbm->put($call, $ref);
329 }
330
331 # freeze the user
332 sub encode
333 {
334         goto &json_encode if $v4;
335         goto &asc_encode unless $v3;
336         my $self = shift;
337         return nfreeze($self);
338 }
339
340 # thaw the user
341 sub decode
342 {
343         goto &json_dncode if $v4;
344         goto &asc_decode unless $v3;
345         my $ref;
346         $ref = thaw(shift);
347         return $ref;
348 }
349
350
351 # create a string from a user reference (in_ascii)
352 #
353 sub asc_encode
354 {
355         my $self = shift;
356         my $strip = shift;
357         my $p;
358
359         if ($strip) {
360                 my $ref = bless {}, ref $self;
361                 foreach my $k (qw(qth lat long qra sort call homenode node lastoper lastin)) {
362                         $ref->{$k} = $self->{$k} if exists $self->{$k};
363                 }
364                 $ref->{name} = $self->{name} if exists $self->{name} && $self->{name} !~ /selfspot/i;
365                 $p = dd($ref);
366         } else {
367                 $p = dd($self);
368         }
369         return $p;
370 }
371
372 #
373 # create a hash from a string (in ascii)
374 #
375 sub asc_decode
376 {
377         my $s = shift;
378         my $ref;
379         $s =~ s/\%([0-9A-F][0-9A-F])/chr(hex($1))/eg;
380         eval '$ref = ' . $s;
381         if ($@) {
382                 LogDbg('err', "DXUser::asc_decode: on '$s' $@");
383                 $ref = undef;
384         }
385         return $ref;
386 }
387
388 sub json_decode
389 {
390
391 }
392
393 sub json_encode
394 {
395
396 }
397         
398 #
399 # del - delete a user
400 #
401
402 sub del
403 {
404         my $self = shift;
405         my $call = $self->{call};
406         $lru->remove($call);
407         $dbm->del($call);
408 }
409
410 #
411 # close - close down a user
412 #
413
414 sub close
415 {
416         my $self = shift;
417         my $startt = shift;
418         my $ip = shift;
419         $self->{lastin} = $main::systime;
420         # add a record to the connect list
421         my $ref = [$startt || $self->{startt}, $main::systime];
422         push @$ref, $ip if $ip;
423         push @{$self->{connlist}}, $ref;
424         shift @{$self->{connlist}} if @{$self->{connlist}} > $maxconnlist;
425         $self->put();
426 }
427
428 #
429 # sync the database
430 #
431
432 sub sync
433 {
434         $dbm->sync;
435 }
436
437 #
438 # return a list of valid elements 
439
440
441 sub fields
442 {
443         return keys(%valid);
444 }
445
446
447 #
448 # export the database to an ascii file
449 #
450
451 sub export
452 {
453         my $name = shift || 'user_asc';
454         my $basic_info_only = shift;
455
456         my $fn = $name ne 'user_asc' ? $name : "$main::local_data/$name";                       # force use of local
457         
458         # save old ones
459         move "$fn.oooo", "$fn.ooooo" if -e "$fn.oooo";
460         move "$fn.ooo", "$fn.oooo" if -e "$fn.ooo";
461         move "$fn.oo", "$fn.ooo" if -e "$fn.oo";
462         move "$fn.o", "$fn.oo" if -e "$fn.o";
463         move "$fn", "$fn.o" if -e "$fn";
464
465         my $count = 0;
466         my $err = 0;
467         my $del = 0;
468         my $fh = new IO::File ">$fn" or return "cannot open $fn ($!)";
469         if ($fh) {
470                 my $key = 0;
471                 my $val = undef;
472                 my $action;
473                 my $t = scalar localtime;
474                 print $fh q{#!/usr/bin/perl
475 #
476 # The exported userfile for a DXSpider System
477 #
478 # Input file: $filename
479 #       Time: $t
480 #
481                         
482 package main;
483                         
484 # search local then perl directories
485 BEGIN {
486         umask 002;
487                                 
488         # root of directory tree for this system
489         $root = "/spider"; 
490         $root = $ENV{'DXSPIDER_ROOT'} if $ENV{'DXSPIDER_ROOT'};
491         
492         unshift @INC, "$root/perl";     # this IS the right way round!
493         unshift @INC, "$root/local";
494         
495         # try to detect a lockfile (this isn't atomic but 
496         # should do for now
497         $lockfn = "$root/local_data/cluster.lck";       # lock file name
498         if (-e $lockfn) {
499                 open(CLLOCK, "$lockfn") or die "Can't open Lockfile ($lockfn) $!";
500                 my $pid = <CLLOCK>;
501                 chomp $pid;
502                 die "Lockfile ($lockfn) and process $pid exists - cluster must be stopped first\n" if kill 0, $pid;
503                 close CLLOCK;
504         }
505 }
506
507 use SysVar;
508 use DXUser;
509
510 if (@ARGV) {
511         $main::userfn = shift @ARGV;
512         print "user filename now $userfn\n";
513 }
514
515 package DXUser;
516
517 del_file();
518 init(1);
519 %u = ();
520 my $count = 0;
521 my $err = 0;
522 while (<DATA>) {
523         chomp;
524         my @f = split /\t/;
525         my $ref = asc_decode($f[1]);
526         if ($ref) {
527                 $ref->put();
528                 $count++;
529         DXUser::sync() unless $count % 10000;
530         } else {
531                 print "# Error: $f[0]\t$f[1]\n";
532                 $err++
533         }
534 }
535 DXUser::sync(); DXUser::finish();
536 print "There are $count user records and $err errors\n";
537 };
538                 print $fh "__DATA__\n";
539
540         for ($action = R_FIRST; !$dbm->seq($key, $val, $action); $action = R_NEXT) {
541                         if (!is_callsign($key) || $key =~ /^0/) {
542                                 my $eval = $val;
543                                 my $ekey = $key;
544                                 $eval =~ s/([\%\x00-\x1f\x7f-\xff])/sprintf("%%%02X", ord($1))/eg; 
545                                 $ekey =~ s/([\%\x00-\x1f\x7f-\xff])/sprintf("%%%02X", ord($1))/eg; 
546                                 LogDbg('DXCommand', "Export Error1: $ekey\t$eval");
547                                 eval {$dbm->del($key)};
548                                 dbg(carp("Export Error1: $ekey\t$eval\n$@")) if $@;
549                                 ++$err;
550                                 next;
551                         }
552                         my $ref;
553                         eval {$ref = decode($val); };
554                         if ($ref) {
555                                 my $t = $ref->{lastin} || 0;
556                                 if ($ref->is_user && !$ref->{priv} && $main::systime > $t + $tooold) {
557                                         unless ($ref->{lat} && $ref->{long} || $ref->{qth} || $ref->{qra}) {
558                                                 eval {$dbm->del($key)};
559                                                 dbg(carp("Export Error2: $key\t$val\n$@")) if $@;
560                                                 LogDbg('DXCommand', "$ref->{call} deleted, too old");
561                                                 $del++;
562                                                 next;
563                                         }
564                                 }
565                                 # only store users that are reasonably active or have useful information
566                                 print $fh "$key\t" . $ref->asc_encode($basic_info_only) . "\n";
567                                 ++$count;
568                         } else {
569                                 LogDbg('DXCommand', "Export Error3: $key\t" . carp($val) ."\n$@");
570                                 eval {$dbm->del($key)};
571                                 dbg(carp("Export Error3: $key\t$val\n$@")) if $@;
572                                 ++$err;
573                         }
574                 } 
575         $fh->close;
576     }
577         my $s = qq{Exported users to $fn - $count Users $del Deleted $err Errors ('sh/log Export' for details)};
578         LogDbg('command', $s);
579         return $s;
580 }
581
582 sub export_json
583 {
584         use Data::Structure::Util qw(unbless);
585         
586         my $name = shift || 'user_json';
587         my $basic_info_only = shift;
588
589         my $fn = $name ne 'user_json' ? $name : "$main::local_data/$name";                       # force use of local
590         
591         # save old ones
592         move "$fn.oooo", "$fn.ooooo" if -e "$fn.oooo";
593         move "$fn.ooo", "$fn.oooo" if -e "$fn.ooo";
594         move "$fn.oo", "$fn.ooo" if -e "$fn.oo";
595         move "$fn.o", "$fn.oo" if -e "$fn.o";
596         move "$fn", "$fn.o" if -e "$fn";
597
598         my $json = JSON->new;
599         $json->canonical(1);
600         $json->allow_blessed(1);
601         
602         my $count = 0;
603         my $err = 0;
604         my $del = 0;
605         my $fh = new IO::File ">$fn" or return "cannot open $fn ($!)";
606         if ($fh) {
607                 my $key = 0;
608                 my $val = undef;
609                 my $action;
610                 my $t = scalar localtime;
611                 print $fh q{#!/usr/bin/perl
612 #
613 # The exported userfile for a DXSpider System
614 #
615 # Input file: $filename
616 #       Time: $t
617 #
618                         
619 package main;
620                         
621 # search local then perl directories
622 BEGIN {
623         umask 002;
624                                 
625         # root of directory tree for this system
626         $root = "/spider"; 
627         $root = $ENV{'DXSPIDER_ROOT'} if $ENV{'DXSPIDER_ROOT'};
628         
629         unshift @INC, "$root/perl";     # this IS the right way round!
630         unshift @INC, "$root/local";
631         
632         # try to detect a lockfile (this isn't atomic but 
633         # should do for now
634         $lockfn = "$root/local_data/cluster.lck";       # lock file name
635         if (-e $lockfn) {
636                 open(CLLOCK, "$lockfn") or die "Can't open Lockfile ($lockfn) $!";
637                 my $pid = <CLLOCK>;
638                 chomp $pid;
639                 die "Lockfile ($lockfn) and process $pid exists - cluster must be stopped first\n" if kill 0, $pid;
640                 close CLLOCK;
641         }
642 }
643
644 use SysVar;
645 use DXUser;
646
647 if (@ARGV) {
648         $main::userfn = shift @ARGV;
649         print "user filename now $userfn\n";
650 }
651
652 package DXUser;
653
654 use JSON;
655 my $json = JSON->new;
656
657 del_file();
658 init(1);
659 %u = ();
660 my $count = 0;
661 my $err = 0;
662 while (<DATA>) {
663         chomp;
664         my @f = split /\t/;
665         my $ref;
666     eval { $ref = $json->decode($f[1]); };
667         if ($ref && !$@) {
668         $ref = bless $ref, 'DXUser';
669                 $ref->put();
670                 $count++;
671         DXUser::sync() unless $count % 10000;
672         } else {
673                 print "# Error: $f[0]\t$f[1]\n";
674                 $err++
675         }
676 }
677 DXUser::sync(); DXUser::finish();
678 print "There are $count user records and $err errors\n";
679 };
680                 print $fh "__DATA__\n";
681
682         for ($action = R_FIRST; !$dbm->seq($key, $val, $action); $action = R_NEXT) {
683                         if (!is_callsign($key) || $key =~ /^0/) {
684                                 my $eval = $val;
685                                 my $ekey = $key;
686                                 $eval =~ s/([\%\x00-\x1f\x7f-\xff])/sprintf("%%%02X", ord($1))/eg; 
687                                 $ekey =~ s/([\%\x00-\x1f\x7f-\xff])/sprintf("%%%02X", ord($1))/eg; 
688                                 LogDbg('DXCommand', "Export Error1: $ekey\t$eval");
689                                 eval {$dbm->del($key)};
690                                 dbg(carp("Export Error1: $ekey\t$eval\n$@")) if $@;
691                                 ++$err;
692                                 next;
693                         }
694                         my $ref;
695                         eval {$ref = decode($val); };
696                         if ($ref) {
697                                 my $t = $ref->{lastin} || 0;
698                                 if ($ref->is_user && !$ref->{priv} && $main::systime > $t + $tooold) {
699                                         unless ($ref->{lat} && $ref->{long} || $ref->{qth} || $ref->{qra}) {
700                                                 eval {$dbm->del($key)};
701                                                 dbg(carp("Export Error2: $key\t$val\n$@")) if $@;
702                                                 LogDbg('DXCommand', "$ref->{call} deleted, too old");
703                                                 $del++;
704                                                 next;
705                                         }
706                                 }
707                                 # only store users that are reasonably active or have useful information
708                                 unbless($ref);
709                                 print $fh "$key\t" . $json->encode($ref) . "\n";
710                                 ++$count;
711                         } else {
712                                 LogDbg('DXCommand', "Export Error3: $key\t" . carp($val) ."\n$@");
713                                 eval {$dbm->del($key)};
714                                 dbg(carp("Export Error3: $key\t$val\n$@")) if $@;
715                                 ++$err;
716                         }
717                 } 
718         $fh->close;
719     }
720         my $s = qq{Exported users to $fn - $count Users $del Deleted $err Errors ('sh/log Export' for details)};
721         LogDbg('command', $s);
722         return $s;
723 }
724
725 #
726 # group handling
727 #
728
729 # add one or more groups
730 sub add_group
731 {
732         my $self = shift;
733         my $ref = $self->{group} || [ 'local' ];
734         $self->{group} = $ref if !$self->{group};
735         push @$ref, @_ if @_;
736 }
737
738 # remove one or more groups
739 sub del_group
740 {
741         my $self = shift;
742         my $ref = $self->{group} || [ 'local' ];
743         my @in = @_;
744         
745         $self->{group} = $ref if !$self->{group};
746         
747         @$ref = map { my $a = $_; return (grep { $_ eq $a } @in) ? () : $a } @$ref;
748 }
749
750 # does this thing contain all the groups listed?
751 sub union
752 {
753         my $self = shift;
754         my $ref = $self->{group};
755         my $n;
756         
757         return 0 if !$ref || @_ == 0;
758         return 1 if @$ref == 0 && @_ == 0;
759         for ($n = 0; $n < @_; ) {
760                 for (@$ref) {
761                         my $a = $_;
762                         $n++ if grep $_ eq $a, @_; 
763                 }
764         }
765         return $n >= @_;
766 }
767
768 # simplified group test just for one group
769 sub in_group
770 {
771         my $self = shift;
772         my $s = shift;
773         my $ref = $self->{group};
774         
775         return 0 if !$ref;
776         return grep $_ eq $s, $ref;
777 }
778
779 # set up a default group (only happens for them's that connect direct)
780 sub new_group
781 {
782         my $self = shift;
783         $self->{group} = [ 'local' ];
784 }
785
786 # set up empty buddies (only happens for them's that connect direct)
787 sub new_buddies
788 {
789         my $self = shift;
790         $self->{buddies} = [  ];
791 }
792
793 #
794 # return a prompt for a field
795 #
796
797 sub field_prompt
798
799         my ($self, $ele) = @_;
800         return $valid{$ele};
801 }
802
803 # some variable accessors
804 sub sort
805 {
806         my $self = shift;
807         @_ ? $self->{'sort'} = shift : $self->{'sort'} ;
808 }
809
810 # some accessors
811
812 # want is default = 1
813 sub _want
814 {
815         my $n = shift;
816         my $self = shift;
817         my $val = shift;
818         my $s = "want$n";
819         $self->{$s} = $val if defined $val;
820         return exists $self->{$s} ? $self->{$s} : 1;
821 }
822
823 # wantnot is default = 0
824 sub _wantnot
825 {
826         my $n = shift;
827         my $self = shift;
828         my $val = shift;
829         my $s = "want$n";
830         $self->{$s} = $val if defined $val;
831         return exists $self->{$s} ? $self->{$s} : 0;
832 }
833
834 sub wantbeep
835 {
836         return _want('beep', @_);
837 }
838
839 sub wantann
840 {
841         return _want('ann', @_);
842 }
843
844 sub wantwwv
845 {
846         return _want('wwv', @_);
847 }
848
849 sub wantwcy
850 {
851         return _want('wcy', @_);
852 }
853
854 sub wantecho
855 {
856         return _want('echo', @_);
857 }
858
859 sub wantwx
860 {
861         return _want('wx', @_);
862 }
863
864 sub wantdx
865 {
866         return _want('dx', @_);
867 }
868
869 sub wanttalk
870 {
871         return _want('talk', @_);
872 }
873
874 sub wantgrid
875 {
876         return _want('grid', @_);
877 }
878
879 sub wantemail
880 {
881         return _want('email', @_);
882 }
883
884 sub wantann_talk
885 {
886         return _want('ann_talk', @_);
887 }
888
889 sub wantpc16
890 {
891         return _want('pc16', @_);
892 }
893
894 sub wantsendpc16
895 {
896         return _want('sendpc16', @_);
897 }
898
899 sub wantroutepc16
900 {
901         return _want('routepc16', @_);
902 }
903
904 sub wantusstate
905 {
906         return _want('usstate', @_);
907 }
908
909 sub wantdxcq
910 {
911         return _want('dxcq', @_);
912 }
913
914 sub wantdxitu
915 {
916         return _want('dxitu', @_);
917 }
918
919 sub wantgtk
920 {
921         return _want('gtk', @_);
922 }
923
924 sub wantpc9x
925 {
926         return _want('pc9x', @_);
927 }
928
929 sub wantlogininfo
930 {
931         my $self = shift;
932         my $val = shift;
933         $self->{wantlogininfo} = $val if defined $val;
934         return $self->{wantlogininfo};
935 }
936
937 sub is_node
938 {
939         my $self = shift;
940         return $self->{sort} =~ /^[ACRSX]$/;
941 }
942
943 sub is_local_node
944 {
945         my $self = shift;
946         return grep $_ eq 'local_node', @{$self->{group}};
947 }
948
949 sub is_user
950 {
951         my $self = shift;
952         return $self->{sort} =~ /^[UW]$/;
953 }
954
955 sub is_web
956 {
957         my $self = shift;
958         return $self->{sort} eq 'W';
959 }
960
961 sub is_bbs
962 {
963         my $self = shift;
964         return $self->{sort} eq 'B';
965 }
966
967 sub is_spider
968 {
969         my $self = shift;
970         return $self->{sort} eq 'S';
971 }
972
973 sub is_clx
974 {
975         my $self = shift;
976         return $self->{sort} eq 'C';
977 }
978
979 sub is_dxnet
980 {
981         my $self = shift;
982         return $self->{sort} eq 'X';
983 }
984
985 sub is_arcluster
986 {
987         my $self = shift;
988         return $self->{sort} eq 'R';
989 }
990
991 sub is_ak1a
992 {
993         my $self = shift;
994         return $self->{sort} eq 'A';
995 }
996
997 sub unset_passwd
998 {
999         my $self = shift;
1000         delete $self->{passwd};
1001 }
1002
1003 sub unset_passphrase
1004 {
1005         my $self = shift;
1006         delete $self->{passphrase};
1007 }
1008
1009 sub set_believe
1010 {
1011         my $self = shift;
1012         my $call = uc shift;
1013         $self->{believe} ||= [];
1014         push @{$self->{believe}}, $call unless grep $_ eq $call, @{$self->{believe}};
1015 }
1016
1017 sub unset_believe
1018 {
1019         my $self = shift;
1020         my $call = uc shift;
1021         if (exists $self->{believe}) {
1022                 $self->{believe} = [grep {$_ ne $call} @{$self->{believe}}];
1023                 delete $self->{believe} unless @{$self->{believe}};
1024         }
1025 }
1026
1027 sub believe
1028 {
1029         my $self = shift;
1030         return exists $self->{believe} ? @{$self->{believe}} : ();
1031 }
1032
1033 sub lastping
1034 {
1035         my $self = shift;
1036         my $call = shift;
1037         $self->{lastping} ||= {};
1038         $self->{lastping} = {} unless ref $self->{lastping};
1039         my $b = $self->{lastping};
1040         $b->{$call} = shift if @_;
1041         return $b->{$call};     
1042 }
1043 1;
1044 __END__
1045
1046
1047
1048
1049