+ my ($self, $line) = @_;
+ my @field = split /[\^\~]/, $line;
+
+ # ignore any lines that don't start with PC
+ return if !$field[0] =~ /^PC/;
+
+ # process PC frames
+ my ($pcno) = $field[0] =~ /^PC(\d\d)/; # just get the number
+ return if $pcno < 10 || $pcno > 51;
+
+ SWITCH: {
+ if ($pcno == 10) { # incoming talk
+
+ # is it for me or one of mine?
+ my $call = ($field[5] gt ' ') ? $field[5] : $field[2];
+ if ($call eq $main::mycall || grep $_ eq $call, get_all_user_calls()) {
+
+ # yes, it is
+ my $text = unpad($field[3]);
+ my $ref = DXChannel->get($call);
+ $ref->send("$call de $field[1]: $text") if $ref;
+ } else {
+ route($field[2], $line); # relay it on its way
+ }
+ return;
+ }
+
+ if ($pcno == 11) { # dx spot
+
+ # if this is a 'nodx' node then ignore it
+ last SWITCH if grep $field[7] =~ /^$_/, @DXProt::nodx_node;
+
+ # convert the date to a unix date
+ my $date = $field[3];
+ my $time = $field[4];
+ $date =~ s/^\s*(\d+)-(\w\w\w)-(19\d\d)$/$1 $2 $3/;
+ $time =~ s/^(\d\d)(\d\d)Z$/$1:$2 +0000/;
+ my $d = str2time("$date $time");
+ return if !$d; # bang out (and don't pass on) if date is invalid
+
+ # strip off the leading & trailing spaces from the comment
+ my $text = unpad($field[5]);
+
+ # store it away
+ Spot::add($field[1], $field[2], $d, $text, $field[6]);
+
+ # format and broadcast it to users
+ my $spotter = $field[6];
+ $spotter =~ s/-\d+$//o; # strip off the ssid from the spotter
+ $spotter .= ':'; # add a colon
+
+ # send orf to the users
+ my $buf = sprintf "DX de %-7.7s %13.13s %-12.12s %-30.30s %5.5s\a\a", $spotter, $field[1], $field[2], $text, $field[4];
+ broadcast_users($buf);
+
+ last SWITCH;
+ }
+
+ if ($pcno == 12) { # announces
+
+ if ($field[2] eq '*' || $field[2] eq $main::mycall) {
+
+ # strip leading and trailing stuff
+ my $text = unpad($field[3]);
+ my $target;
+ my @list;
+
+ if ($field[4] eq '*') { # sysops
+ $target = "To Sysops";
+ @list = map { $_->priv >= 5 ? $_ : () } get_all_users();
+ } elsif ($field[4] gt ' ') { # speciality list handling
+ my ($name) = split /\./, $field[4];
+ $target = "To $name"; # put the rest in later (if bothered)
+ }
+
+ $target = "WX" if $field[6] eq '1';
+ $target = "To All" if !$target;
+
+ if (@list > 0) {
+ broadcast_list("$target de $field[1]: $text", @list);
+ } else {
+ broadcast_users("$target de $field[1]: $text");
+ }
+
+ return if $field[2] eq $main::mycall; # it's routed to me
+ } else {
+ route($field[2], $line);
+ return; # only on a routed one
+ }
+
+ last SWITCH;
+ }
+
+ if ($pcno == 13) {last SWITCH;}
+ if ($pcno == 14) {last SWITCH;}
+ if ($pcno == 15) {last SWITCH;}
+
+ if ($pcno == 16) { # add a user
+ my $node = DXCluster->get($field[1]);
+ last SWITCH if !$node; # ignore if havn't seen a PC19 for this one yet
+ my $i;
+
+ for ($i = 2; $i < $#field; $i++) {
+ my ($call, $confmode, $here) = $field[$i] =~ /^(\w+) (-) (\d)/o;
+ next if length $call < 3;
+ next if !$confmode;
+ $call = uc $call;
+ $call =~ s/-\d+$//o; # remove ssid
+ next if DXCluster->get($call); # we already have this (loop?)
+
+ $confmode = $confmode eq '*';
+ DXNodeuser->new($self, $node, $call, $confmode, $here);
+
+ # add this station to the user database, if required
+ my $user = DXUser->get_current($call);
+ $user = DXUser->new($call) if !$user;
+ $user->node($node->call) if !$user->node;
+ $user->put;
+ }
+ last SWITCH;
+ }
+
+ if ($pcno == 17) { # remove a user
+ my $ref = DXCluster->get($field[1]);
+ $ref->del() if $ref;
+ last SWITCH;
+ }
+
+ if ($pcno == 18) { # link request
+
+ $self->send_local_config();
+ $self->send(pc20());
+ last SWITCH;
+ }
+
+ if ($pcno == 19) { # incoming cluster list
+ my $i;
+ for ($i = 1; $i < $#field-1; $i += 4) {
+ my $here = $field[$i];
+ my $call = uc $field[$i+1];
+ my $confmode = $field[$i+2] eq '*';
+ my $ver = $field[$i+3];