change the generatation of PC16/17 a bit
[spider.git] / perl / DXCommandmode.pm
1 #!/usr/bin/perl
2 #
3 # This module impliments the user facing command mode for a dx cluster
4 #
5 # Copyright (c) 1998 Dirk Koopman G1TLH
6 #
7 # $Id$
8
9
10 package DXCommandmode;
11
12 use POSIX;
13
14 @ISA = qw(DXChannel);
15
16 use DXUtil;
17 use DXChannel;
18 use DXUser;
19 use DXVars;
20 use DXDebug;
21 use DXM;
22 use DXLog;
23 use DXLogPrint;
24 use DXBearing;
25 use CmdAlias;
26 use Filter;
27 use Minimuf;
28 use DXDb;
29 use AnnTalk;
30 use WCY;
31 use Sun;
32 use Internet;
33 use Script;
34
35
36 use strict;
37 use vars qw(%Cache %cmd_cache $errstr %aliases $scriptbase $maxerrors %nothereslug $maxbadcount);
38
39 %Cache = ();                                    # cache of dynamically loaded routine's mod times
40 %cmd_cache = ();                                # cache of short names
41 $errstr = ();                                   # error string from eval
42 %aliases = ();                                  # aliases for (parts of) commands
43 $scriptbase = "$main::root/scripts"; # the place where all users start scripts go
44 $maxerrors = 20;                                # the maximum number of concurrent errors allowed before disconnection
45 $maxbadcount = 3;                               # no of bad words allowed before disconnection
46
47
48 use vars qw($VERSION $BRANCH);
49 $VERSION = sprintf( "%d.%03d", q$Revision$ =~ /(\d+)\.(\d+)/ );
50 $BRANCH = sprintf( "%d.%03d", q$Revision$ =~ /\d+\.\d+\.(\d+)\.(\d+)/ ) || 0;
51 $main::build += $VERSION;
52 $main::branch += $BRANCH;
53
54 #
55 # obtain a new connection this is derived from dxchannel
56 #
57
58 sub new 
59 {
60         my $self = DXChannel::alloc(@_);
61
62         # routing, this must go out here to prevent race condx
63         my $pkg = shift;
64         my $call = shift;
65         my @rout = $main::routeroot->add_user($call, Route::here(1));
66
67         # ALWAYS output the user
68         my $ref = Route::User::get($call);
69         DXProt::route_pc16($main::me, $main::routeroot, $ref) if $ref;
70
71         return $self;
72 }
73
74 # this is how a a connection starts, you get a hello message and the motd with
75 # possibly some other messages asking you to set various things up if you are
76 # new (or nearly new and slacking) user.
77
78 sub start
79
80         my ($self, $line, $sort) = @_;
81         my $user = $self->{user};
82         my $call = $self->{call};
83         my $name = $user->{name};
84         
85         # log it
86         my $host = $self->{conn}->{peerhost} || "unknown";
87         Log('DXCommand', "$call connected from $host");
88
89         $self->{name} = $name ? $name : $call;
90         $self->send($self->msg('l2',$self->{name}));
91         $self->state('prompt');         # a bit of room for further expansion, passwords etc
92         $self->{priv} = $user->priv || 0;
93         $self->{lang} = $user->lang || $main::lang || 'en';
94         $self->{pagelth} = $user->pagelth || 20;
95         ($self->{width}) = $line =~ /width=(\d+)/; $line =~ s/\s*width=\d+\s*//;
96         $self->{width} = 80 unless $self->{width} && $self->{width} > 80;
97         $self->{consort} = $line;       # save the connection type
98         
99         # set some necessary flags on the user if they are connecting
100         $self->{beep} = $user->wantbeep;
101         $self->{ann} = $user->wantann;
102         $self->{wwv} = $user->wantwwv;
103         $self->{wcy} = $user->wantwcy;
104         $self->{talk} = $user->wanttalk;
105         $self->{wx} = $user->wantwx;
106         $self->{dx} = $user->wantdx;
107         $self->{logininfo} = $user->wantlogininfo;
108         $self->{ann_talk} = $user->wantann_talk;
109         $self->{here} = 1;
110
111         # sort out registration
112         if ($main::reqreg == 1) {
113                 $self->{registered} = $user->registered;
114         } elsif ($main::reqreg == 2) {
115                 $self->{registered} = !$user->registered;
116         } else {
117                 $self->{registered} = 1;
118         }
119
120
121         # decide which motd to send
122         my $motd = "${main::motd}_nor" unless $self->{registered};
123         $motd = $main::motd unless $motd && -e $motd;
124         $self->send_file($motd) if -e $motd;
125
126         # sort out privilege reduction
127         $self->{priv} = 0 if $line =~ /^(ax|te)/ && !$self->conn->{usedpasswd};
128
129         # get the filters
130         $self->{spotsfilter} = Filter::read_in('spots', $call, 0) || Filter::read_in('spots', 'user_default', 0);
131         $self->{wwvfilter} = Filter::read_in('wwv', $call, 0) || Filter::read_in('wwv', 'user_default', 0);
132         $self->{wcyfilter} = Filter::read_in('wcy', $call, 0) || Filter::read_in('wcy', 'user_default', 0);
133         $self->{annfilter} = Filter::read_in('ann', $call, 0) || Filter::read_in('ann', 'user_default', 0) ;
134
135         # clean up qra locators
136         my $qra = $user->qra;
137         $qra = undef if ($qra && !DXBearing::is_qra($qra));
138         unless ($qra) {
139                 my $lat = $user->lat;
140                 my $long = $user->long;
141                 $user->qra(DXBearing::lltoqra($lat, $long)) if (defined $lat && defined $long);  
142         }
143
144         # decide on echo
145         my $echo = $user->wantecho;
146         unless ($echo) {
147                 $self->send_now('E', "0");
148                 $self->send($self->msg('echow'));
149                 $self->conn->echo($echo) if $self->conn->can('echo');
150         }
151         
152         $self->tell_login('loginu');
153         
154         # do we need to send a forward/opernam?
155         my $lastoper = $user->lastoper || 0;
156         my $homenode = $user->homenode || ""; 
157         if ($homenode eq $main::mycall && $lastoper + $DXUser::lastoperinterval < $main::systime) {
158                 run_cmd($main::me, "forward/opernam $call");
159                 $user->lastoper($main::systime);
160         }
161
162         # run a script send the output to the punter
163         my $script = new Script(lc $call) || new Script('user_default');
164         $script->run($self) if $script;
165
166         # send cluster info
167         my $info = Route::cluster();
168         $self->send("Cluster:$info");
169
170         # send prompts and things
171         $self->send($self->msg('namee1')) if !$user->name;
172         $self->send($self->msg('qthe1')) if !$user->qth;
173         $self->send($self->msg('qll')) if !$user->qra || (!$user->lat && !$user->long);
174         $self->send($self->msg('hnodee1')) if !$user->qth;
175         $self->send($self->msg('m9')) if DXMsg::for_me($call);
176         $self->prompt;
177 }
178
179 #
180 # This is the normal command prompt driver
181 #
182
183 sub normal
184 {
185         my $self = shift;
186         my $cmdline = shift;
187         my @ans;
188         
189         # remove leading and trailing spaces
190         $cmdline =~ s/^\s*(.*)\s*$/$1/;
191         
192         if ($self->{state} eq 'page') {
193                 my $i = $self->{pagelth};
194                 my $ref = $self->{pagedata};
195                 my $tot = @$ref;
196                 
197                 # abort if we get a line starting in with a
198                 if ($cmdline =~ /^a/io) {
199                         undef $ref;
200                         $i = 0;
201                 }
202         
203                 # send a tranche of data
204                 while ($i-- > 0 && @$ref) {
205                         my $line = shift @$ref;
206                         $line =~ s/\s+$//o;     # why am having to do this? 
207                         $self->send($line);
208                 }
209                 
210                 # reset state if none or else chuck out an intermediate prompt
211                 if ($ref && @$ref) {
212                         $tot -= $self->{pagelth};
213                         $self->send($self->msg('page', $tot));
214                 } else {
215                         $self->state('prompt');
216                 }
217         } elsif ($self->{state} eq 'sysop') {
218                 my $passwd = $self->{user}->passwd;
219                 if ($passwd) {
220                         my @pw = grep {$_ !~ /\s/} split //, $passwd;
221                         my @l = @{$self->{passwd}};
222                         my $str = "$pw[$l[0]].*$pw[$l[1]].*$pw[$l[2]].*$pw[$l[3]].*$pw[$l[4]]";
223                         if ($cmdline =~ /$str/) {
224                                 $self->{priv} = $self->{user}->priv;
225                         } else {
226                                 $self->send($self->msg('sorry'));
227                         }
228                 } else {
229                         $self->send($self->msg('sorry'));
230                 }
231                 $self->state('prompt');
232         } elsif ($self->{state} eq 'passwd') {
233                 my $passwd = $self->{user}->passwd;
234                 if ($passwd && $cmdline eq $passwd) {
235                         $self->send($self->msg('pw1'));
236                         $self->state('passwd1');
237                 } else {
238                         $self->conn->{echo} = $self->conn->{decho};
239                         delete $self->conn->{decho};
240                         $self->send($self->msg('sorry'));
241                         $self->state('prompt');
242                 }
243         } elsif ($self->{state} eq 'passwd1') {
244                 $self->{passwd} = $cmdline;
245                 $self->send($self->msg('pw2'));
246                 $self->state('passwd2');
247         } elsif ($self->{state} eq 'passwd2') {
248                 if ($cmdline eq $self->{passwd}) {
249                         $self->{user}->passwd($cmdline);
250                         $self->send($self->msg('pw3'));
251                 } else {
252                         $self->send($self->msg('pw4'));
253                 }
254                 $self->conn->{echo} = $self->conn->{decho};
255                 delete $self->conn->{decho};
256                 $self->state('prompt');
257         } elsif ($self->{state} eq 'talk') {
258                 if ($cmdline =~ m{^(?:/EX|/ABORT)}i) {
259                         for (@{$self->{talklist}}) {
260                                 $self->send_talks($_,  $self->msg('talkend'));
261                         }
262                         $self->state('prompt');
263                         delete $self->{talklist};
264                 } elsif ($cmdline =~ m(^/\w+)) {
265                         $cmdline =~ s(^/)();
266                         $self->send_ans(run_cmd($self, $cmdline));
267                         $self->send($self->talk_prompt);
268                 } elsif ($self->{talklist} && @{$self->{talklist}}) {
269                         # send what has been said to whoever is in this person's talk list
270                         my @bad;
271                         if (@bad = BadWords::check($cmdline)) {
272                                 $self->badcount(($self->badcount||0) + @bad);
273                                 Log('DXCommand', "$self->{call} swore: $cmdline");
274                         } else {
275                                 for (@{$self->{talklist}}) {
276                                         $self->send_talks($_, $cmdline);
277                                 }
278                         }
279                         $self->send($self->talk_prompt) if $self->{state} eq 'talk';
280                 } else {
281                         # for safety
282                         $self->state('prompt');
283                 }
284         } elsif (my $func = $self->{func}) {
285                 no strict 'refs';
286                 my @ans;
287                 if (ref $self->{edit}) {
288                         eval { @ans = $self->{edit}->$func($self, $cmdline)};
289                 } else {
290                         eval {  @ans = &{$self->{func}}($self, $cmdline) };
291                 }
292                 if ($@) {
293                         $self->send_ans("Syserr: on stored func $self->{func}", $@);
294                         delete $self->{func};
295                         $self->state('prompt');
296                         undef $@;
297                 }
298                 $self->send_ans(@ans);
299         } else {
300                 $self->send_ans(run_cmd($self, $cmdline));
301         } 
302
303         # check for excessive swearing
304         if ($self->{badcount} && $self->{badcount} >= $maxbadcount) {
305                 Log('DXCommand', "$self->{call} logged out for excessive swearing");
306                 $self->disconnect;
307                 return;
308         }
309
310         # send a prompt only if we are in a prompt state
311         $self->prompt() if $self->{state} =~ /^prompt/o;
312 }
313
314 # send out the talk messages taking into account vias and connectivity
315 sub send_talks
316 {
317         my ($self, $ent, $line) = @_;
318         
319         my ($to, $via) = $ent =~ /(\S+)>(\S+)/;
320         $to = $ent unless $to;
321         my $call = $via ? $via : $to;
322         my $clref = Route::get($call);
323         my $dxchan = $clref->dxchan if $clref;
324         if ($dxchan) {
325                 $dxchan->talk($self->{call}, $to, $via, $line);
326         } else {
327                 $self->send($self->msg('disc2', $via ? $via : $to));
328                 my @l = grep { $_ ne $ent } @{$self->{talklist}};
329                 if (@l) {
330                         $self->{talklist} = \@l;
331                 } else {
332                         delete $self->{talklist};
333                         $self->state('prompt');
334                 }
335         }
336 }
337
338 sub talk_prompt
339 {
340         my $self = shift;
341         my @call;
342         for (@{$self->{talklist}}) {
343                 my ($to, $via) = /(\S+)>(\S+)/;
344                 $to = $_ unless $to;
345                 push @call, $to;
346         }
347         return $self->msg('talkprompt', join(',', @call));
348 }
349
350 #
351 # send a load of stuff to a command user with page prompting
352 # and stuff
353 #
354
355 sub send_ans
356 {
357         my $self = shift;
358         
359         if ($self->{pagelth} && @_ > $self->{pagelth}) {
360                 my $i;
361                 for ($i = $self->{pagelth}; $i-- > 0; ) {
362                         my $line = shift @_;
363                         $line =~ s/\s+$//o;     # why am having to do this? 
364                         $self->send($line);
365                 }
366                 $self->{pagedata} =  [ @_ ];
367                 $self->state('page');
368                 $self->send($self->msg('page', scalar @_));
369         } else {
370                 for (@_) {
371                         if (defined $_) {
372                                 $self->send($_);
373                         } else {
374                                 $self->send('');
375                         }
376                 }
377         } 
378 }
379
380 # this is the thing that runs the command, it is done like this for the 
381 # benefit of remote command execution
382 #
383
384 sub run_cmd
385 {
386         my $self = shift;
387         my $user = $self->{user};
388         my $call = $self->{call};
389         my $cmdline = shift;
390         my @ans;
391         
392
393         return () if length $cmdline == 0;
394                 
395         # strip out //
396         $cmdline =~ s|//|/|og;
397                 
398         # split the command line up into parts, the first part is the command
399         my ($cmd, $args) = split /\s+/, $cmdline, 2;
400         $args = "" unless defined $args;
401                 
402         if ($cmd) {
403                         
404                 my ($path, $fcmd);
405                         
406                 dbg("cmd: $cmd") if isdbg('command');
407                         
408                 # alias it if possible
409                 my $acmd = CmdAlias::get_cmd($cmd);
410                 if ($acmd) {
411                         ($cmd, $args) = split /\s+/, "$acmd $args", 2;
412                         $args = "" unless defined $args;
413                         dbg("aliased cmd: $cmd $args") if isdbg('command');
414                 }
415                         
416                 # first expand out the entry to a command
417                 ($path, $fcmd) = search($main::localcmd, $cmd, "pl");
418                 ($path, $fcmd) = search($main::cmd, $cmd, "pl") if !$path || !$fcmd;
419
420                 if ($path && $cmd) {
421                         dbg("path: $cmd cmd: $fcmd") if isdbg('command');
422                         
423                         my $package = find_cmd_name($path, $fcmd);
424                         return ($@) if $@;
425                                 
426                         if ($package) {
427                                 no strict 'refs';
428                                 dbg("package: $package") if isdbg('command');
429                                 eval { @ans = &$package($self, $args) };
430                                 return (DXDebug::shortmess($@)) if $@;
431                         }
432                 } else {
433                         dbg("cmd: $cmd not found") if isdbg('command');
434                         if (++$self->{errors} > $maxerrors) {
435                                 $self->send($self->msg('e26'));
436                                 $self->disconnect;
437                                 return ();
438                         } else {
439                                 return ($self->msg('e1'));
440                         }
441                 }
442         }
443         
444         my $ok = shift @ans;
445         if ($ok) {
446                 delete $self->{errors};
447         } else {
448                 if (++$self->{errors} > $maxerrors) {
449                         $self->send($self->msg('e26'));
450                         $self->disconnect;
451                         return ();
452                 }
453         }
454         return (@ans);
455 }
456
457 #
458 # This is called from inside the main cluster processing loop and is used
459 # for despatching commands that are doing some long processing job
460 #
461 sub process
462 {
463         my $t = time;
464         my @dxchan = DXChannel->get_all();
465         my $dxchan;
466         
467         foreach $dxchan (@dxchan) {
468                 next if $dxchan->sort ne 'U';  
469                 
470                 # send a prompt if no activity out on this channel
471                 if ($t >= $dxchan->t + $main::user_interval) {
472                         $dxchan->prompt() if $dxchan->{state} =~ /^prompt/o;
473                         $dxchan->t($t);
474                 }
475         }
476
477         while (my ($k, $v) = each %nothereslug) {
478                 if ($main::systime >= $v + 300) {
479                         delete $nothereslug{$k};
480                 }
481         }
482 }
483
484 #
485 # finish up a user context
486 #
487 sub disconnect
488 {
489         my $self = shift;
490         my $call = $self->call;
491
492         return if $self->{disconnecting}++;
493
494         delete $self->{senddbg};
495
496         my $uref = Route::User::get($call);
497         my @rout;
498         if ($uref) {
499                 @rout = $main::routeroot->del_user($uref);
500                 dbg("B/C PC17 on $main::mycall for: $call") if isdbg('route');
501
502                 # issue a pc17 to everybody interested
503                 DXProt::route_pc17($main::me, $main::routeroot, $uref);
504         } else {
505                 confess "trying to disconnect a non existant user $call";
506         }
507
508         # I was the last node visited
509     $self->user->node($main::mycall);
510                 
511         # send info to all logged in thingies
512         $self->tell_login('logoutu');
513
514         Log('DXCommand', "$call disconnected");
515
516         $self->SUPER::disconnect;
517 }
518
519 #
520 # short cut to output a prompt
521 #
522
523 sub prompt
524 {
525         my $self = shift;
526         $self->send($self->msg($self->here ? 'pr' : 'pr2', $self->call, cldate($main::systime), ztime($main::systime)));
527 }
528
529 # broadcast a message to all users [except those mentioned after buffer]
530 sub broadcast
531 {
532         my $pkg = shift;                        # ignored
533         my $s = shift;                          # the line to be rebroadcast
534         
535     foreach my $dxchan (DXChannel->get_all()) {
536                 next unless $dxchan->{sort} eq 'U'; # only interested in user channels  
537                 next if grep $dxchan == $_, @_;
538                 $dxchan->send($s);                      # send it
539         }
540 }
541
542 # gimme all the users
543 sub get_all
544 {
545         return grep {$_->{sort} eq 'U'} DXChannel->get_all();
546 }
547
548 # run a script for this user
549 sub run_script
550 {
551         my $self = shift;
552         my $silent = shift || 0;
553         
554 }
555
556 #
557 # search for the command in the cache of short->long form commands
558 #
559
560 sub search
561 {
562         my ($path, $short_cmd, $suffix) = @_;
563         my ($apath, $acmd);
564         
565         # commands are lower case
566         $short_cmd = lc $short_cmd;
567         dbg("command: $path $short_cmd\n") if isdbg('command');
568
569         # do some checking for funny characters
570         return () if $short_cmd =~ /\/$/;
571
572         # return immediately if we have it
573         ($apath, $acmd) = split ',', $cmd_cache{$short_cmd} if $cmd_cache{$short_cmd};
574         if ($apath && $acmd) {
575                 dbg("cached $short_cmd = ($apath, $acmd)\n") if isdbg('command');
576                 return ($apath, $acmd);
577         }
578         
579         # if not guess
580         my @parts = split '/', $short_cmd;
581         my $dirfn;
582         my $curdir = $path;
583         my $p;
584         my $i;
585         my @lparts;
586         
587         for ($i = 0; $i < @parts; $i++) {
588                 my  $p = $parts[$i];
589                 opendir(D, $curdir) or confess "can't open $curdir $!";
590                 my @ls = readdir D;
591                 closedir D;
592                 my $l;
593                 foreach $l (sort @ls) {
594                         next if $l =~ /^\./;
595                         if ($i < $#parts) {             # we are dealing with directories
596                                 if ((-d "$curdir/$l") && $p eq substr($l, 0, length $p)) {
597                                         dbg("got dir: $curdir/$l\n") if isdbg('command');
598                                         $dirfn .= "$l/";
599                                         $curdir .= "/$l";
600                                         last;
601                                 }
602                         } else {                        # we are dealing with commands
603                                 @lparts = split /\./, $l;                  
604                                 next if $lparts[$#lparts] ne $suffix;        # only look for .$suffix files
605                                 if ($p eq substr($l, 0, length $p)) {
606                                         pop @lparts; #  remove the suffix
607                                         $l = join '.', @lparts;
608                                         #                 chop $dirfn;               # remove trailing /
609                                         $dirfn = "" unless $dirfn;
610                                         $cmd_cache{$short_cmd} = join(',', ($path, "$dirfn$l")); # cache it
611                                         dbg("got path: $path cmd: $dirfn$l\n") if isdbg('command');
612                                         return ($path, "$dirfn$l"); 
613                                 }
614                         }
615                 }
616         }
617         return ();  
618 }  
619
620 # clear the command name cache
621 sub clear_cmd_cache
622 {
623         no strict 'refs';
624         
625         for (keys %Cache) {
626                 undef *{$_};
627                 dbg("Undefining cmd $_") if isdbg('command');
628         }
629         %cmd_cache = ();
630         %Cache = ();
631 }
632
633 #
634 # the persistant execution of things from the command directories
635 #
636 #
637 # This allows perl programs to call functions dynamically
638
639 # This has been nicked directly from the perlembed pages
640 #
641
642 #require Devel::Symdump;  
643
644 sub valid_package_name {
645         my($string) = @_;
646         $string =~ s|([^A-Za-z0-9_/])|sprintf("_%2x",unpack("C",$1))|eg;
647         
648         $string =~ s|/|_|g;
649         return "cmd_$string";
650 }
651
652
653 # this bit of magic finds a command in the offered directory
654 sub find_cmd_name {
655         my $path = shift;
656         my $cmdname = shift;
657         my $package = valid_package_name($cmdname);
658         my $filename = "$path/$cmdname.pl";
659         my $mtime = -M $filename;
660         
661         # return if we can't find it
662         $errstr = undef;
663         unless (defined $mtime) {
664                 $errstr = DXM::msg('e1');
665                 return undef;
666         }
667         
668         if(defined $Cache{$package}->{mtime} &&$Cache{$package}->{mtime} <= $mtime) {
669                 #we have compiled this subroutine already,
670                 #it has not been updated on disk, nothing left to do
671                 #print STDERR "already compiled $package->handler\n";
672                 ;
673         } else {
674
675                 my $sub = readfilestr($filename);
676                 unless ($sub) {
677                         $errstr = "Syserr: can't open '$filename' $!";
678                         return undef;
679                 };
680                 
681                 #wrap the code into a subroutine inside our unique package
682                 my $eval = qq( sub $package { $sub } );
683                 
684                 if (isdbg('eval')) {
685                         my @list = split /\n/, $eval;
686                         my $line;
687                         for (@list) {
688                                 dbg($_ . "\n") if isdbg('eval');
689                         }
690                 }
691                 
692                 # get rid of any existing sub and try to compile the new one
693                 no strict 'refs';
694
695                 if (exists $Cache{$package}) {
696                         dbg("Redefining $package") if isdbg('command');
697                         undef *$package;
698                 } else {
699                         dbg("Defining $package") if isdbg('command');
700                 }
701                 eval $eval;
702                 
703                 $Cache{$package} = {mtime => $mtime };
704             
705         }
706
707         return $package;
708 }
709
710 sub local_send
711 {
712         my ($self, $let, $buf) = @_;
713         if ($self->{state} eq 'prompt' || $self->{state} eq 'talk') {
714                 if ($self->{enhanced}) {
715                         $self->send_later($let, $buf);
716                 } else {
717                         $self->send($buf);
718                 }
719         } else {
720                 $self->delay($buf);
721         }
722 }
723
724 # send a talk message here
725 sub talk
726 {
727         my ($self, $from, $to, $via, $line) = @_;
728         $line =~ s/\\5E/\^/g;
729         $self->local_send('T', "$to de $from: $line") if $self->{talk};
730         Log('talk', $to, $from, $via?$via:$main::mycall, $line);
731         # send a 'not here' message if required
732         unless ($self->{here} && $from ne $to) {
733                 my $key = "$to$from";
734                 unless (exists $nothereslug{$key}) {
735                         my ($ref, $dxchan);
736                         if (($ref = Route::get($from)) && ($dxchan = $ref->dxchan)) {
737                                 my $name = $self->user->name || $to;
738                                 my $s = $self->user->nothere || $dxchan->msg('nothere', $name);
739                                 $nothereslug{$key} = $main::systime;
740                                 $dxchan->talk($to, $from, undef, $s);
741                         }
742                 }
743         }
744 }
745
746 # send an announce
747 sub announce
748 {
749         my $self = shift;
750         my $line = shift;
751         my $isolate = shift;
752         my $to = shift;
753         my $target = shift;
754         my $text = shift;
755         my ($filter, $hops);
756
757         if (!$self->{ann_talk} && $to ne $self->{call}) {
758                 my $call = AnnTalk::is_talk_candidate($_[0], $text);
759                 return if $call;
760         }
761
762         if ($self->{annfilter}) {
763                 ($filter, $hops) = $self->{annfilter}->it(@_ );
764                 return unless $filter;
765         }
766
767         unless ($self->{ann}) {
768                 return if $_[0] ne $main::myalias && $_[0] ne $main::mycall;
769         }
770         return if $target eq 'SYSOP' && $self->{priv} < 5;
771         my $buf = "$to$target de $_[0]: $text";
772         $buf =~ s/\%5E/^/g;
773         $buf .= "\a\a" if $self->{beep};
774         $self->local_send($target eq 'WX' ? 'W' : 'N', $buf);
775 }
776
777 # send a dx spot
778 sub dx_spot
779 {
780         my $self = shift;
781         my $line = shift;
782         my $isolate = shift;
783         my ($filter, $hops);
784
785         return unless $self->{dx};
786         
787         if ($self->{spotsfilter}) {
788                 ($filter, $hops) = $self->{spotsfilter}->it(@_ );
789                 return unless $filter;
790         }
791
792
793         my $t = ztime($_[2]);
794         my $ref = DXUser->get_current($_[4]);
795         my $loc = $ref->qra if $ref && $ref->qra && $self->{user}->wantgrid;
796         $loc = ' ' . substr($loc, 0, 4) if $loc;
797         $loc = "" unless $loc;
798         my $buf = sprintf "DX de %-7.7s%11.1f  %-12.12s %-*s $t$loc", "$_[4]:", $_[0], $_[1], $self->{consort} eq 'local' ? 29 : 30, $_[3];
799         $buf .= "\a\a" if $self->{beep};
800         $buf =~ s/\%5E/^/g;
801         $self->local_send('X', $buf);
802 }
803
804 sub wwv
805 {
806         my $self = shift;
807         my $line = shift;
808         my $isolate = shift;
809         my ($filter, $hops);
810
811         return unless $self->{wwv};
812         
813         if ($self->{wwvfilter}) {
814                 ($filter, $hops) = $self->{wwvfilter}->it(@_ );
815                 return unless $filter;
816         }
817
818         my $buf = "WWV de $_[6] <$_[1]>:   SFI=$_[2], A=$_[3], K=$_[4], $_[5]";
819         $buf .= "\a\a" if $self->{beep};
820         $self->local_send('V', $buf);
821 }
822
823 sub wcy
824 {
825         my $self = shift;
826         my $line = shift;
827         my $isolate = shift;
828         my ($filter, $hops);
829
830         return unless $self->{wcy};
831         
832         if ($self->{wcyfilter}) {
833                 ($filter, $hops) = $self->{wcyfilter}->it(@_ );
834                 return unless $filter;
835         }
836
837         my $buf = "WCY de $_[10] <$_[1]> : K=$_[4] expK=$_[5] A=$_[3] R=$_[6] SFI=$_[2] SA=$_[7] GMF=$_[8] Au=$_[9]";
838         $buf .= "\a\a" if $self->{beep};
839         $self->local_send('Y', $buf);
840 }
841
842 # broadcast debug stuff to all interested parties
843 sub broadcast_debug
844 {
845         my $s = shift;                          # the line to be rebroadcast
846         
847         foreach my $dxchan (DXChannel->get_all) {
848                 next unless $dxchan->{enhanced} && $dxchan->{senddbg};
849                 $dxchan->send_later('L', $s);
850         }
851 }
852
853
854 1;
855 __END__