BBS-Universal

 view release on metacpan or  search on metacpan

lib/BBS/Universal.pm  view on Meta::CPAN


sub is_connected {
    my $self = shift;

    if ($self->{'sysop'} || $self->{'local_mode'}) {
        return (TRUE);
    } elsif ($self->{'CACHE'}->get('RUNNING') && defined($self->{'cl_socket'})) {
        $self->{'CACHE'}->set(sprintf('SERVER_%02d', $self->{'thread_number'}), 'CONNECTED');
        $self->{'CACHE'}->set('UPDATE',                                         TRUE);
        return (TRUE);
    } else {
        $self->{'debug'}->WARNING(['User disconnected']);
        $self->{'CACHE'}->set(sprintf('SERVER_%02d', $self->{'thread_number'}), 'IDLE');
        $self->{'CACHE'}->set('UPDATE',                                         TRUE);
        return (FALSE);
    } ## end else [ if ($self->{'sysop'} ||...)]
} ## end sub is_connected

sub decision {
    my $self = shift;

    $self->{'debug'}->DEBUG(['Start Decision']);
    my $response = uc($self->get_key(SILENT, BLOCKING));
    if ($response eq 'Y') {
        $self->output("YES\n");
        $self->{'debug'}->DEBUG(['  Decision YES']);
        return (TRUE);
    }
    $self->{'debug'}->DEBUG(['  Decision NO']);
    $self->output("NO\n");
    $self->{'debug'}->DEBUG(['End Decision']);
    return (FALSE);
} ## end sub decision

sub prompt {
    my $self = shift;
    my $text = shift;

    $self->{'debug'}->DEBUG(['Start Prompt', "  Prompt > $text"]);
    my $response = "\n";
    if ($self->{'USER'}->{'text_mode'} eq 'ATASCII') {
        $response .= '(' . colored(['bright_yellow'], $self->{'USER'}->{'username'}) . ') ' . $text . chr(31) . ' ';
    } elsif ($self->{'USER'}->{'text_mode'} eq 'PETSCII') {
        $response .= '(' . $self->{'USER'}->{'username'} . ') ' . "$text > ";
    } elsif ($self->{'USER'}->{'text_mode'} eq 'ANSI') {
        $response .= '(' . colored(['bright_yellow'], $self->{'USER'}->{'username'}) . ') ' . $text . ' [% BLACK RIGHT-POINTING TRIANGLE %] ';
    } else {
        $response .= '(' . $self->{'USER'}->{'username'} . ') ' . "$text > ";
    }
    $self->output($response);
    $self->{'debug'}->DEBUG(['End Prompt']);
    return (TRUE);
} ## end sub prompt

sub menu_choice {
    my $self   = shift;
    my $choice = shift;
    my $color  = shift;
    my $desc   = shift;

    $self->{'debug'}->DEBUG(['Start Menu Choice']);
    if ($self->{'USER'}->{'text_mode'} eq 'ATASCII') {
        $self->output(" $choice " . chr(31) . " $desc");
    } elsif ($self->{'USER'}->{'text_mode'} eq 'PETSCII') {
        $self->output(" $choice > $desc");
    } elsif ($self->{'USER'}->{'text_mode'} eq 'ANSI') {
        $self->output(charnames::string_vianame('BOX DRAWINGS LIGHT VERTICAL') . '[% ' . $color . ' %]' . $choice . '[% RESET %]' . charnames::string_vianame('BOX DRAWINGS LIGHT VERTICAL') . '[% ' . $color . ' %]' . charnames::string_vianame('BLACK R...
    } else {
        $self->output(" $choice > $desc");
    }
    $self->{'debug'}->DEBUG(['End Menu Choice']);
} ## end sub menu_choice

sub show_choices {
    my $self    = shift;
    my $mapping = shift;

    $self->{'debug'}->DEBUG(['Start Show Choices']);
    my @list = grep(!/TEXT/, (sort(keys %{$mapping})));
    my $twin = FALSE;
    $twin = TRUE if (scalar(@list) > 1 && $self->{'USER'}->{'max_columns'} > 40);
    my $max = 0;
    foreach my $name (@list) {
        $max = max(length($mapping->{$name}->{'text'}), $max);
    }
    if ($self->{'USER'}->{'text_mode'} eq 'ANSI') {
        if ($twin) {
            $max += 3;
            $self->output(sprintf("%s%s%s%-${max}s %s%s%s\t", '[% BOX DRAWINGS LIGHT ARC DOWN AND RIGHT %]', '[% BOX DRAWINGS LIGHT HORIZONTAL %]', '[% BOX DRAWINGS LIGHT ARC DOWN AND LEFT %]', ' ' x $max, '[% BOX DRAWINGS LIGHT ARC DOWN AND RIGHT %]...
        } else {
            $self->output('[% BOX DRAWINGS LIGHT ARC DOWN AND RIGHT %][% BOX DRAWINGS LIGHT HORIZONTAL %][% BOX DRAWINGS LIGHT ARC DOWN AND LEFT %]' . "\n");
        }
    } ## end if ($self->{'USER'}->{...})
    while (scalar(@list)) {
        my $kmenu = shift(@list);
        if ($self->{'access_level'}->{ $mapping->{$kmenu}->{'access_level'} } <= $self->{'access_level'}->{ $self->{'USER'}->{'access_level'} }) {
            if ($twin) {
                $self->menu_choice($kmenu, $mapping->{$kmenu}->{'color'}, $mapping->{$kmenu}->{'text'} . ' ' x (($max - 1) - length($mapping->{$kmenu}->{'text'})));
                if (scalar(@list)) {
                    $kmenu = shift(@list);
                    $self->menu_choice($kmenu, $mapping->{$kmenu}->{'color'}, $mapping->{$kmenu}->{'text'});
                } elsif ($self->{'USER'}->{'text_mode'} eq 'ANSI') {
                    $self->output(sprintf('%s%s%s', '[% BOX DRAWINGS LIGHT ARC UP AND RIGHT %]', '[% BOX DRAWINGS LIGHT HORIZONTAL %]', '[% BOX DRAWINGS LIGHT ARC UP AND LEFT %]'));
                    $twin = FALSE;
                }
            } else {
                $self->menu_choice($kmenu, $mapping->{$kmenu}->{'color'}, $mapping->{$kmenu}->{'text'});
            }
            $self->output("\n");
        } ## end if ($self->{'access_level'...})
    } ## end while (scalar(@list))
    if ($self->{'USER'}->{'text_mode'} eq 'ANSI') {
        if ($twin) {
            $self->output(sprintf("%s%s%s%-${max}s %s%s%s", '[% BOX DRAWINGS LIGHT ARC UP AND RIGHT %]', '[% BOX DRAWINGS LIGHT HORIZONTAL %]', '[% BOX DRAWINGS LIGHT ARC UP AND LEFT %]', ' ' x $max, '[% BOX DRAWINGS LIGHT ARC UP AND RIGHT %]', '[% B...
        } else {
            $self->output('[% BOX DRAWINGS LIGHT ARC UP AND RIGHT %][% BOX DRAWINGS LIGHT HORIZONTAL %][% BOX DRAWINGS LIGHT ARC UP AND LEFT %]');
        }
    } ## end if ($self->{'USER'}->{...})
    $self->{'debug'}->DEBUG(['End Show Choices']);
} ## end sub show_choices

sub header {
    my $self = shift;

    $self->{'debug'}->DEBUG(['Start Header']);
    my $width = $self->{'USER'}->{'max_columns'};
    my $name  = ' ' . $self->{'CONF'}->{'BBS NAME'} . ' ';

    my $text = '#' x int(($width - length($name)) / 2);
    $text .= $name;
    $text .= '#' x ($width - length($text));
    if ($self->{'USER'}->{'text_mode'} eq 'ANSI') {
        my $char = '[% BOX DRAWINGS HEAVY HORIZONTAL %]';
        $text =~ s/\#/$char/g;
    }
    $self->{'debug'}->DEBUG(['End Header']);
    return ($self->detokenize_text('[% CLS %]' . $text));
} ## end sub header

sub load_menu {
    my $self = shift;
    my $file = shift;

    $self->{'debug'}->DEBUG(['Start Load Menu', "  Load Menu $file"]);
    my $orig    = $self->files_load_file($file);
    my @Text    = split(/\n/, $orig);
    my $mapping = { 'TEXT' => '' };
    my $mode    = TRUE;
    my $text    = '';
    $self->{'debug'}->DEBUG(['  Parse Menu']);
    foreach my $line (@Text) {
        if ($mode) {
            next if ($line =~ /^\#/);
            if ($line !~ /^---/) {
                my ($k, $cmd, $color, $access, $t) = split(/\|/, $line);
                $k     = uc($k);
                $cmd   = uc($cmd);
                $color = uc($color);
                if (exists($self->{'COMMANDS'}->{$cmd})) {
                    $mapping->{$k} = {
                        'command'      => $cmd,
                        'color'        => $color,
                        'access_level' => $access,
                        'text'         => $t,
                    };
                } else {
                    $self->{'debug'}->ERROR(["Command Missing!  $cmd"]);
                }
            } else {
                $mode = FALSE;
            }
        } else {
            $mapping->{'TEXT'} .= $self->detokenize_text($line) . "\n";
        }
    } ## end foreach my $line (@Text)
    $mapping->{'TEXT'} = $self->header() . "\n" . $mapping->{'TEXT'};
    $self->{'debug'}->DEBUG(['End Load Menu']);
    return ($mapping);
} ## end sub load_menu

sub main_menu {
    my $self = shift;
    my $file = shift;

    $self->{'debug'}->DEBUG(['Start Main Menu']);
    my $connected = TRUE;
    my $command   = '';
    my $mapping   = $self->load_menu($file);
    while ($connected && $self->is_connected()) {
        $self->output($mapping->{'TEXT'});
        $self->show_choices($mapping);
        $self->prompt('Choose');
        my $key;
        do {
            $key = uc($self->get_key(SILENT, FALSE));
        } until (exists($mapping->{$key}) || $key eq chr(3) || !$self->is_connected());
        $self->output($mapping->{$key}->{'command'} . "\n");
        if ($key eq chr(3)) {
            $command = 'DISCONNECT';
        } else {
            $command = $mapping->{$key}->{'command'};
        }
        $mapping = $self->{'COMMANDS'}->{$command}->($self);
        if (ref($mapping) ne 'HASH' || !$self->is_connected()) {
            $connected = FALSE;
        }
    } ## end while ($connected && $self...)
    $self->{'debug'}->DEBUG(['End Main Menu']);
} ## end sub main_menu

sub disconnect {
    my $self = shift;

    $self->{'debug'}->DEBUG(['Start Disconnect']);

    # Load and print disconnect message here
    my $text = $self->files_load_file('files/main/disconnect');
    $self->output($text);
    my $sth = $self->{'dbh'}->prepare('UPDATE users SET logout_time=NOW() WHERE id=?');
    $sth->execute($self->{'USER'}->{'id'});
    $sth->finish();
    $self->{'debug'}->DEBUG(['End Disconnect']);
    return (TRUE);
} ## end sub disconnect

sub parse_telnet_escape {
    my $self    = shift;
    my $command = shift;
    my $option  = shift;
    my $handle  = $self->{'cl_socket'};

    $self->{'debug'}->DEBUG(['Start Parse Telnet Escape']);
    if ($command == WILL) {
        if ($option == ECHO) {    # WON'T ECHO
            print $handle chr(IAC) . chr(WONT) . chr(ECHO);
        } elsif ($option == LINEMODE) {
            print $handle chr(IAC) . chr(WONT) . chr(LINEMODE);
        }
    } elsif ($command == DO) {
        if ($option == ECHO) {    # DON'T ECHO
            print $handle chr(IAC) . chr(DONT) . chr(ECHO);
        } elsif ($option == LINEMODE) {
            print $handle chr(IAC) . chr(DONT) . chr(LINEMODE);
        }
    } else {
        $self->{'debug'}->DEBUG(['Recreived IAC Request - ' . $self->{'telnet_commands'}->[$command - 240] . ' : ' . $self->{'telnet_options'}->[$option]]);
    }
    $self->{'debug'}->DEBUG(['End Parse Telnet Escape']);
    return (TRUE);
} ## end sub parse_telnet_escape

sub flush_input {
    my $self = shift;

    my $key;
    unless ($self->{'sysop'} || $self->{'local_mode'}) {
        my $handle = $self->{'cl_socket'};
        ReadMode 'noecho', $handle;
        do {
            $key = ReadKey(-1, $handle);
        } until (!defined($key) || $key eq '');
        ReadMode 'restore', $handle;
    } else {
        ReadMode 'ultra-raw';
        do {
            $key = ReadKey(-1);
        } until (!defined($key) || $key eq '');
        ReadMode 'restore';

lib/BBS/Universal.pm  view on Meta::CPAN

        $sth->execute('%' . $filter . '%', '%' . $filter . '%', $self->{'USER'}->{'file_category'});
    } else {
        $sth = $self->{'dbh'}->prepare('SELECT * FROM files_view WHERE category_id=? ORDER BY uploaded DESC');
        $sth->execute($self->{'USER'}->{'file_category'});
    }
    my @files;
    my $max_filename = 10;
    my $max_title    = 20;
    if ($sth->rows > 0) {
        while (my $row = $sth->fetchrow_hashref()) {
            push(@files, $row);
            $max_filename = max(length($row->{'filename'}), $max_filename);
            $max_title    = max(length($row->{'title'}),    $max_title);
        }
        my $table = Text::SimpleTable->new($max_filename, $max_title);
        $table->row('FILENAME', 'TITLE');
        $table->hr();
        foreach my $record (@files) {
            $table->row($record->{'filename'}, $record->{'title'});
        }
        my $mode = $self->{'USER'}->{'text_mode'};
        if ($mode eq 'ANSI') {
            my $text = $table->boxes2('MAGENTA')->draw();
            while ($text =~ / (FILENAME|TITLE) /s) {
                my $ch  = $1;
                my $new = '[% BRIGHT YELLOW %]' . $ch . '[% RESET %]';
                $text =~ s/ $ch / $new /gs;
            }
            $self->output("\n$text");
        } elsif ($mode eq 'ATASCII') {
            $self->output("\n" . $self->color_border($table->boxes->draw(), 'MAGENTA'));
        } elsif ($mode eq 'PETSCII') {
            my $text = $table->boxes->draw();
            while ($text =~ / (FILENAME|TITLE) /s) {
                my $ch  = $1;
                my $new = '[% YELLOW %]' . $ch . '[% RESET %]';
                $text =~ s/ $ch / $new /gs;
            }
            $self->output("\n" . $self->color_border($text, 'PURPLE'));
        } else {
            $self->output("\n" . $table->draw());
        }
    } elsif ($search) {
        $self->output("\nSorry '$filter' not found");
    } else {
        $self->output("\nSorry, this file category is empty\n");
    }
    $self->output("\nPress a key to continue ...");
    $self->get_key(ECHO, BLOCKING);
    $self->{'debug'}->DEBUG(['End Files List Summary']);
    return (TRUE);
} ## end sub files_list_summary

sub files_choices {
    my ($self, $record) = @_;

    while ($self->is_connected()) {
        my $view    = FALSE;
        my $mapping = {
            'TEXT' => '',
            'Z'    => { 'command' => 'BACK',        'color' => 'WHITE', 'access_level' => 'USER',         'text' => 'Return to File Menu' },
            'N'    => { 'command' => 'NEXT',        'color' => 'BLUE',  'access_level' => 'USER',         'text' => 'Next file' },
            'D'    => { 'command' => 'DOWNLOAD',    'color' => 'CYAN',  'access_level' => 'VETERAN',      'text' => 'Download file' },
            'R'    => { 'command' => 'REMOVE FILE', 'color' => 'RED',   'access_level' => 'JUNIOR SYSOP', 'text' => 'Remove file' },
        };
        if ($record->{'extension'} =~ /^(TXT|ASC|ATA|PET|VT|ANS|MD|INF|CDF|PL|PM|PY|C|CPP|H|SH|CSS|HTM|HTML|SHTML|JS|JAVA|XML|BAT)$/ && $self->check_access_level('VETERAN')) {
            $view = TRUE;
            $mapping->{'V'} = { 'command' => 'VIEW FILE', 'color' => 'CYAN', 'access_level' => 'VETERAN', 'text' => 'View file' };
        } ## end if ($record->{'extension'...})
        $self->show_choices($mapping);
        $self->prompt('Choose');
        my $key;
        do {
            $key = uc($self->get_key());
        } until ($key =~ /D|N|Z/ || ($key eq 'V' && $view) || ($key eq 'R' && $self->check_access_level('JUNION SYSOP')));
        $self->output($mapping->{$key}->{'command'} . "\n");
        if ($mapping->{$key}->{'command'} eq 'DOWNLOAD') {
            my $file = $self->{'CONF'}->{'BBS ROOT'} . '/' . $self->{'CONF'}->{'FILES PATH'} . '/' . $self->{'USER'}->{'file_category_path'} . '/' . $record->{'filename'};
            $mapping = {
                'B' => { 'command' => 'BACK',   'color' => 'WHITE',       'access_level' => 'USER',    'text' => 'Return to File Menu' },
                'Y' => { 'command' => 'YMODEM', 'color' => 'YELLOW',      'access_level' => 'VETERAN', 'text' => 'Download with the Ymodem protocol' },
                'X' => { 'command' => 'XMODEM', 'color' => 'BRIGHT BLUE', 'access_level' => 'VETERAN', 'text' => 'Download with the Xmodem protocol' },
                'Z' => { 'command' => 'ZMODEM', 'color' => 'GREEN',       'access_level' => 'VETERAN', 'text' => 'Download with the Zmodem protocol' },
            };
            $self->show_choices($mapping);
            $self->prompt('Choose');
            do {
                $key = uc($self->get_key());
            } until ($key =~ /B|X|Y|Z/);
            $self->output($mapping->{$key}->{'command'});
            if ($mapping->{$key}->{'command'} eq 'XMODEM') {
                system('sz', '--xmodem', '--quiet', '--binary', $file);
            } elsif ($mapping->{$key}->{'command'} eq 'YMODEM') {
                system('sz', '--ymodem', '--quiet', '--binary', $file);
            } elsif ($mapping->{$key}->{'command'} eq 'ZMODEM') {
                system('sz', '--zmodem', '--quiet', '--binary', '--resume', $file);
            } else {
                return (FALSE);
            }
            return (TRUE);
        } elsif ($mapping->{$key}->{'command'} eq 'VIEW FILE' && $self->check_access_level($mapping->{$key}->{'access_level'})) {
            my $file = $self->{'CONF'}->{'BBS ROOT'} . '/' . $self->{'CONF'}->{'FILES PATH'} . '/' . $self->{'USER'}->{'file_category_path'} . '/' . $record->{'filename'};
            open(my $VIEW, '<', $file);
            binmode($VIEW, ":encoding(UTF-8)");
            my $data;
            read($VIEW, $data, $record->{'file_size'}, 0);
            close($VIEW);
            $self->output('[% CLS %]' . $data . '[% RESET %]');
        } elsif ($mapping->{$key}->{'command'} eq 'REMOVE FILE' && $self->check_access_level($mapping->{$key}->{'access_level'})) {
            return (TRUE);
        } elsif ($mapping->{$key}->{'command'} eq 'NEXT') {
            return (TRUE);
        } elsif ($mapping->{$key}->{'command'} eq 'BACK') {
            return (FALSE);
        }
    } ## end while ($self->is_connected...)
} ## end sub files_choices

sub files_upload_choices {
    my ($self) = @_;
    my $ckey;

    $self->prompt('File Name? ');
    my $file = $self->get_line({ 'type' => FILENAME, 'max' => 255, 'default' => '' });
    my $ext  = uc($file =~ /\.(.*?)$/);

    $self->prompt('Title (Fiendly name)? ');
    my $title = $self->get_line({ 'type' => STRING, 'max' => 255, 'default' => '' });

    $self->prompt('Description? ');
    my $description = $self->get_line({ 'type' => STRING, 'max' => 255, 'default' => '' });

    my $file_category = $self->{'USER'}->{'file_category'};

    my $mapping = {
        'B' => { 'command' => 'BACK',   'color' => 'WHITE',       'access_level' => 'USER',    'text' => 'Return to File Menu' },
        'Y' => { 'command' => 'YMODEM', 'color' => 'YELLOW',      'access_level' => 'VETERAN', 'text' => 'Upload with the Ymodem protocol' },
        'X' => { 'command' => 'XMODEM', 'color' => 'BRIGHT BLUE', 'access_level' => 'VETERAN', 'text' => 'Upload with the Xmodem protocol' },
        'Z' => { 'command' => 'ZMODEM', 'color' => 'GREEN',       'access_level' => 'VETERAN', 'text' => 'Upload with the Zmodem protocol' },
    };
    $self->show_choices($mapping);
    $self->prompt('Choose');
    do {
        $ckey = uc($self->get_key());
    } until ($ckey =~ /B|X|Y|Z/);
    $self->output($mapping->{$ckey}->{'command'});
    if ($mapping->{$ckey}->{'command'} eq 'XMODEM') {
        if ($self->files_receive_file($file, XMODEM)) {
            my $filename = $self->{'CONF'}->{'BBS ROOT'} . '/' . $self->{'CONF'}->{'FILES PATH'} . '/' . $self->{'USER'}->{'file_category_path'} . '/' . $file;
            my $size     = (-s $filename);
            my $sth      = $self->{'dbh'}->prepare('INSERT INTO files (category,filename,title,file_type,description,file_size) VALUES (?,?,?,(SELECT id FROM file_types WHERE extension=?),?,?');
            $sth->execute($file_category, $file, $title, $ext, $description, $size);
            $sth->finish();
        } ## end if ($self->files_receive_file...)
    } elsif ($mapping->{$ckey}->{'command'} eq 'YMODEM') {
        if ($self->files_receive_file($file, YMODEM)) {
            my $filename = $self->{'CONF'}->{'BBS ROOT'} . '/' . $self->{'CONF'}->{'FILES PATH'} . '/' . $self->{'USER'}->{'file_category_path'} . '/' . $file;
            my $size     = (-s $filename);
            my $sth      = $self->{'dbh'}->prepare('INSERT INTO files (category,filename,title,file_type,description,file_size) VALUES (?,?,?,(SELECT id FROM file_types WHERE extension=?),?,?');
            $sth->execute($file_category, $file, $title, $ext, $description, $size);
            $sth->finish();
        } ## end if ($self->files_receive_file...)
    } elsif ($mapping->{$ckey}->{'command'} eq 'ZMODEM') {
        if ($self->files_receive_file($file, ZMODEM)) {
            my $filename = $self->{'CONF'}->{'BBS ROOT'} . '/' . $self->{'CONF'}->{'FILES PATH'} . '/' . $self->{'USER'}->{'file_category_path'} . '/' . $file;
            my $size     = (-s $filename);
            my $sth      = $self->{'dbh'}->prepare('INSERT INTO files (category,filename,title,file_type,description,file_size) VALUES (?,?,?,(SELECT id FROM file_types WHERE extension=?),?,?');
            $sth->execute($file_category, $file, $title, $ext, $description, $size);
            $sth->finish();
        } ## end if ($self->files_receive_file...)
    } else {
        return (FALSE);
    }
    if ($? == -1) {
        $self->{'debug'}->ERROR(["Could not execute rz:  $!"]);
    } elsif ($? & 127) {
        $self->{'debug'}->ERROR(["File Transfer Aborted:  $!"]);
    } else {
        $self->{'debug'}->DEBUG(['File Transfer Successful']);
    }
    return (TRUE);
} ## end sub files_upload_choices

sub files_list_detailed {
    my ($self, $search) = @_;

    $self->{'debug'}->DEBUG(['Start Files List Detailed']);
    my $sth;
    my $filter;
    my $columns = $self->{'USER'}->{'max_columns'};
    if ($search) {
        $self->prompt('Search for');
        $filter = $self->get_line({ 'type' => STRING, 'max' => 255, 'default' => '' });
        $sth    = $self->{'dbh'}->prepare('SELECT * FROM files_view WHERE (filename LIKE ? OR title LIKE ?) AND category_id=? ORDER BY uploaded DESC');
        $sth->execute('%' . $filter . '%', '%' . $filter . '%', $self->{'USER'}->{'file_category'});
    } else {

lib/BBS/Universal.pm  view on Meta::CPAN

} ## end sub files_send_zmodem

sub files_receive_file_zmodem {
    my ($self, $file) = @_;
    $self->{'debug'}->DEBUG(['Start files_receive_file_zmodem (using lrzsz)']);

    my $sock = $self->{'cl_socket'};
    unless ($sock) {
        $self->{'debug'}->ERROR(["No client socket for ZMODEM receive"]);
        return 0;
    }

    $self->output("\nStart Zmodem file upload\n");

    # When rz receives files it writes them into the current working directory.
    # Use the destination directory from config (same place other uploads are stored).
    my $dest_dir = $self->{'CONF'}->{'BBS ROOT'} . '/' . $self->{'CONF'}->{'FILES PATH'} . '/' . $self->{'USER'}->{'file_category_path'};

    # ensure directory exists
    unless (-d $dest_dir) {
        File::Path::mkpath($dest_dir);
        if ($@) {
            $self->{'debug'}->ERROR(["Failed to create dest dir $dest_dir: $@"]);
            return 0;
        }
    } ## end unless (-d $dest_dir)

    # We will chdir in the child before exec so the received file lands in $dest_dir.
    # Use rz --binary --quiet. Depending on lrzsz version you may want --overwrite or --keep
    my @args = ('--binary', '--overwrite', '--quiet');

    my $ok = $self->_run_on_socket('rz', \@args, $dest_dir);

    $self->output("\nFile upload complete\n");
    $self->{'debug'}->DEBUG(['End files_receive_file_zmodem (using lrzsz)']);
    return $ok;
} ## end sub files_receive_file_zmodem

 

# package BBS::Universal::Messages;

sub messages_initialize {
    my $self = shift;
    $self->{'debug'}->DEBUG(['Start Messages Initialize']);
    $self->{'debug'}->DEBUG(['End Messages Initialize']);
    return ($self);
} ## end sub messages_initialize

sub messages_forum_categories {
    my $self = shift;

    $self->{'debug'}->DEBUG(['Start Messages Forum Categories']);
    my $command = '';
    my $id;
    my $sth      = $self->{'dbh'}->prepare('SELECT * FROM message_categories ORDER BY description');
    my $category = $self->{'USER'}->{'forum_category'};
    $sth->execute();    # $self->{'USER'}->{'forum_category'});
    my $mapping = {
        'TEXT' => '',
        'Z'    => { 'command' => 'BACK', 'color' => 'WHITE', 'access_level' => 'USER', 'text' => 'Return to Forum Menu' },
    };
    my @menu_choices = @{ $self->{'MENU CHOICES'} };

    while (my $result = $sth->fetchrow_hashref()) {
        if ($self->check_access_level($result->{'access_level'})) {
            $mapping->{ shift(@menu_choices) } = {
                'command'      => $result->{'name'},
                'id'           => $result->{'id'},
                'color'        => ($category == $result->{'id'}) ? 'GREEN' : 'WHITE',
                'access_level' => $result->{'access_level'},
                'text'         => $result->{'description'},
            };
        } ## end if ($self->check_access_level...)
    } ## end while (my $result = $sth->...)
    $sth->finish();
    $self->show_choices($mapping);
    $self->prompt('Choose Forum Category');
    my $key;
    do {
        $key = uc($self->get_key(SILENT, BLOCKING));
    } until (exists($mapping->{$key}) || $key eq chr(3) || !$self->is_connected());
    if ($key eq chr(3)) {
        $command = 'DISCONNECT';
    } else {
        $id      = $mapping->{$key}->{'id'};
        $command = $mapping->{$key}->{'command'};
    }
    return ($command) if ($key eq 'Z');
    if ($self->is_connected() && $command ne 'DISCONNECT') {
        $self->output($command);
        $sth = $self->{'dbh'}->prepare('UPDATE users SET forum_category=? WHERE id=?');
        $sth->execute($id, $self->{'USER'}->{'id'});
        $sth->finish();
        $self->{'USER'}->{'forum_category'} = $id;
        $command = 'BACK';
    } ## end if ($self->is_connected...)
    $self->{'debug'}->DEBUG(['End Messages Forum Categories']);
    return ($command);
} ## end sub messages_forum_categories

sub messages_list_messages {
    my $self = shift;

    $self->{'debug'}->DEBUG(['Start Messages List Messages']);
    my $id;
    my $command;
    my $forum_category = $self->{'USER'}->{'forum_category'};
    my $sth            = $self->{'dbh'}->prepare('SELECT id,from_id,category,author_fullname,author_nickname,author_username,title,created FROM messages_view WHERE category=? ORDER BY created DESC');
    my @index;
    $sth->execute($forum_category);
    if ($sth->rows()) {
        while (my $record = $sth->fetchrow_hashref) {
            push(@index, $record);
        }
        $sth->finish();
        my $result;
        my $count = 0;
        do {
            $result = $index[$count];
            $sth    = $self->{'dbh'}->prepare('SELECT message FROM messages_view WHERE id=? ORDER BY created DESC');
            $sth->execute($result->{'id'});
            $result->{'message'} = $sth->fetchrow_array();
            $sth->finish();
            my $mode = $self->{'USER'}->{'text_mode'};
            if ($mode eq 'ANSI') {
                $self->output("[% CLS %][% HORIZONTAL RULE MAGENTA %][% B_MAGENTA %][% BLACK %]" . $self->pad_center('FORUM MESSAGE' . $self->{'USER'}->{'max_columns'}) . "[% RESET %]\n");
                $self->output('[% B_BRIGHT GREEN %][% BLACK %] CATEGORY [% RESET %] [% BOLD %][% GREEN %][% FORUM CATEGORY %][% RESET %]' . "\n");
                $self->output('[% BRIGHT WHITE %][% B_BLUE %]   Author [% RESET %] ');
                $self->output(($result->{'prefer_nickname'}) ? $result->{'author_nickname'} : $result->{'author_fullname'});
                $self->output(' (' . $result->{'author_username'} . ')' . "\n");
                $self->output('[% BRIGHT WHITE %][% B_BLUE %]    Title [% RESET %] ' . $result->{'title'} . "\n");
                $self->output('[% BRIGHT WHITE %][% B_BLUE %]  Created [% RESET %] ' . $self->users_get_date($result->{'created'}) . "\n\n");
                $self->output($result->{'message'}) if ($self->{'USER'}->{'read_message'});
                $self->output("\n[% HORIZONTAL RULE MAGENTA %]\n");
            } elsif ($mode eq 'PETSCII') {
                $self->output("[% CLS %][% GREEN %]== FORUM " . '=' x ($self->{'USER'}->{'max_columns'} - 7) . "[% RESET %]\n");
                $self->output('[% GREEN   %] CATEGORY [% RESET %] [% FORUM CATEGORY %]' . "\n");
                $self->output('[% YELLOW %]   Author [% RESET %] ');
                $self->output(($result->{'prefer_nickname'}) ? $result->{'author_nickname'} : $result->{'author_fullname'});
                $self->output(' (' . $result->{'author_username'} . ')' . "\n");
                $self->output('[% YELLOW %]    Title [% RESET %] ' . $result->{'title'} . "\n");
                $self->output('[% YELLOW %]  Created [% RESET %] ' . $self->users_get_date($result->{'created'}) . "\n\n");
                $self->output($result->{'message'}) if ($self->{'USER'}->{'read_message'});
                $self->output("\n[% GREEN %]" . '=' x $self->{'USER'}->{'max_columns'} . "[% RESET %]\n");
            } else {
                $self->output("[% CLS %]== FORUM " . '=' x ($self->{'USER'}->{'max_columns'} - 7) . "\n");
                $self->output(' CATEGORY > [% FORUM CATEGORY %]' . "\n");
                $self->output('  Author:  ');
                $self->output(($result->{'prefer_nickname'}) ? $result->{'nickname'} : $result->{'author_fullname'});
                $self->output(' (' . $result->{'author_username'} . ')' . "\n");
                $self->output('   Title:  ' . $result->{'title'} . "\n");
                $self->output(' Created:  ' . $self->users_get_date($result->{'created'}) . "\n\n");
                $self->output($result->{'message'}) if ($self->{'USER'}->{'read_message'});
                $self->output("\n" . '=' x $self->{'USER'}->{'max_columns'} . "\n");
            } ## end else [ if ($mode eq 'ANSI') ]
            my $mapping = {
                'Z' => { 'id' => $result->{'id'}, 'command' => 'BACK', 'color' => 'WHITE',       'access_level' => 'USER', 'text' => 'Return to the Forum Menu' },
                'N' => { 'id' => $result->{'id'}, 'command' => 'NEXT', 'color' => 'BRIGHT BLUE', 'access_level' => 'USER', 'text' => 'Next Message' },
            };
            if ($self->{'USER'}->{'post_message'}) {
                $mapping->{'R'} = { 'id' => $result->{'id'}, 'command' => 'REPLY', 'color' => 'BRIGHT GREEN', 'access_level' => 'USER', 'text' => 'Reply' };
            } ## end if ($self->{'USER'}->{...})
            if ($self->{'USER'}->{'remove_message'}) {
                $mapping->{'D'} = { 'id' => $result->{'id'}, 'command' => 'DELETE', 'color' => 'RED', 'access_level' => 'JUNIOR SYSOP', 'text' => 'Delete Message' };
            } ## end if ($self->{'USER'}->{...})
            $self->show_choices($mapping);
            $self->prompt('Choose');
            my $key;
            do {
                $key = uc($self->get_key(SILENT, FALSE));
            } until (exists($mapping->{$key}) || $key eq chr(3) || !$self->is_connected());
            if ($key eq chr(3)) {
                $id      = undef;
                $command = 'DISCONNECT';
            } else {
                $id      = $mapping->{$key}->{'id'};
                $command = $mapping->{$key}->{'command'};
            }
            $self->output($command);
            if ($command eq 'REPLY') {
                my $message = $self->messages_edit_message('REPLY', $result);
                push(@index, $message);
                $count = 0;
            } elsif ($command eq 'DELETE') {
                $self->messages_delete_message($result);
                delete($index[$count]);
            } else {
                $count++;
            }
            unless ($self->{'local_mode'} || $self->{'sysop'} || $self->is_connected()) {
                $command = 'DISCONNECT';
            }
        } until ($count >= scalar(@index) || $command =~ /^(DISCONNECT|BACK)$/);
    } else {
		$self->output("\nNo messages\n\nPress any key\n");
		$self->get_key(SILENT, BLOCKING);
	} # end if ($sth->rows())
    $self->{'debug'}->DEBUG(['End Messages List Messages']);
    return (TRUE);
} ## end sub messages_list_messages

sub messages_edit_message {
    my $self        = shift;
    my $mode        = shift;
    my $old_message = (scalar(@_)) ? shift : undef;

    $self->{'debug'}->DEBUG(['Start Messages Edit Message']);
    my $message;
    if ($mode eq 'ADD') {
        $self->{'debug'}->DEBUG(['  Add Message']);
        $self->output("Add New Message\n");
        $message = $self->messages_text_editor();
        if (defined($message)) {
            $message->{'from_id'}  = $self->{'USER'}->{'id'};
            $message->{'category'} = $self->{'USER'}->{'forum_category'};
            my $sth = $self->{'dbh'}->prepare('INSERT INTO messages (category, from_id, title, message) VALUES (?, ?, ?, ?)');
            $sth->execute($message->{'category'}, $message->{'from_id'}, $message->{'title'}, $message->{'message'});

lib/BBS/Universal.pm  view on Meta::CPAN

    my $sql = q{
          SELECT news_id,
                 news_title,
                 news_content,
                 DATE_FORMAT(news_date,?) AS newsdate
            FROM news
        ORDER BY news_date DESC};
    my $sth = $self->{'dbh'}->prepare($sql);
    $sth->execute($format);

    if ($sth->rows > 0) {
        my $table = Text::SimpleTable->new(10, $self->{'USER'}->{'max_columns'} - 14);
        $table->row('DATE', 'TITLE');
        $table->hr();
        while (my $row = $sth->fetchrow_hashref()) {
            $table->row($row->{'newsdate'}, $row->{'news_title'});
        }
        my $mode = $self->{'USER'}->{'text_mode'};
        if ($mode eq 'ANSI') {
            my $text = $table->boxes2('BRIGHT BLUE')->draw();
            my $ch   = colored(['bright_yellow'], 'DATE');
            $text =~ s/DATE/$ch/;
            $ch = colored(['bright_yellow'], 'TITLE');
            $text =~ s/TITLE/$ch/;
            $self->output($text);
        } elsif ($mode eq 'ATASCII') {
            my $text = $self->color_border($table->boxes->draw(), 'BLUE');
            $self->output($text);
        } elsif ($mode eq 'PETSCII') {
            my $text = $table->boxes->draw();
            while ($text =~ / (DATE|TITLE) /s) {
                my $ch  = $1;
                my $new = '[% YELLOW %]' . $ch . '[% RESET %]';
                $text =~ s/ $ch / $new /gs;
            }
            $text = $self->color_border($text, 'LIGHT BLUE');
            $self->output($text);
        } else {
            $self->output($table->draw());
        }
    } else {
        $self->output('No News');
    }
    $sth->finish();
    $self->output("\nPress a key to continue ... ");
    $self->get_key(SILENT, BLOCKING);
    $self->{'debug'}->DEBUG(['End News Summary']);
    return (TRUE);
} ## end sub news_summary

sub news_rss_categories {
    my $self = shift;

    $self->{'debug'}->DEBUG(['Start News RSS Categories']);
    my $command = '';
    my $id;
    my $sth = $self->{'dbh'}->prepare('SELECT * FROM rss_feed_categories WHERE id<>? ORDER BY description');
    $sth->execute($self->{'USER'}->{'rss_category'});
    my $mapping = {
        'TEXT' => '',
        'Z'    => { 'command' => 'BACK', 'color' => 'WHITE', 'access_level' => 'USER', 'text' => 'Return to News Menu' },
    };
    my @menu_choices = @{$self->{'MENU CHOICES'}};

    while (my $result = $sth->fetchrow_hashref()) {
        if ($self->check_access_level($result->{'access_level'})) {
            $mapping->{ shift(@menu_choices) } = {
                'command'      => $result->{'title'},
                'id'           => $result->{'id'},
                'color'        => 'WHITE',
                'access_level' => $result->{'access_level'},
                'text'         => $result->{'description'},
            };
        } ## end if ($self->check_access_level...)
    } ## end while (my $result = $sth->...)
    $sth->finish();
    $self->show_choices($mapping);
    $self->prompt('Choose World News Feed Category');
    my $key;
    do {
        $key = uc($self->get_key(SILENT, BLOCKING));
    } until (exists($mapping->{$key}) || $key eq chr(3) || !$self->is_connected());
    if ($key eq chr(3)) {
        return ('DISCONNECT');
    } else {
        $id      = $mapping->{$key}->{'id'};
        $command = $mapping->{$key}->{'command'};
    }
    if ($self->is_connected() && $command ne 'BACK') {
        $self->output($command);
        $sth = $self->{'dbh'}->prepare('UPDATE users SET rss_category=? WHERE id=?');
        $sth->execute($id, $self->{'USER'}->{'id'});
        if ($sth->err) {
            $self->{'debug'}->ERROR([$sth->errstr]);
        }
        $sth->finish();
        $self->{'USER'}->{'rss_category'} = $id;
        $command = 'BACK';
    } ## end if ($self->is_connected...)
    $self->{'debug'}->DEBUG(['End News RSS Categories']);
    return ($command);
} ## end sub news_rss_categories

sub news_rss_feeds {
    my $self = shift;

    $self->{'debug'}->DEBUG(['Start News RSS Feeds']);
    my $mode = $self->{'USER'}->{'text_mode'};
    my $sth  = $self->{'dbh'}->prepare('SELECT * FROM rss_view WHERE category=? ORDER BY title');
    $sth->execute($self->{'USER'}->{'rss_category'});
    my $mapping = {
        'TEXT' => '',
        'Z'    => { 'command' => 'BACK', 'color' => 'WHITE', 'access_level' => 'USER', 'text' => 'Return to News Menu' },
    };
    my @menu_choices = @{$self->{'MENU CHOICES'}};
    while (my $result = $sth->fetchrow_hashref()) {
        if ($self->check_access_level($result->{'access_level'})) {
            $mapping->{ shift(@menu_choices) } = {
                'command'      => $result->{'title'},
                'id'           => $result->{'id'},
                'color'        => 'WHITE',
                'access_level' => $result->{'access_level'},
                'text'         => $result->{'title'},
                'url'          => $result->{'url'},
            };
        } ## end if ($self->check_access_level...)
    } ## end while (my $result = $sth->...)
    $sth->finish();
    $self->show_choices($mapping);
    $self->prompt('Choose World News Feed');
    my $id;
    my $key;
    my $command;
    my $url;
    my $text;
    do {
        $key = uc($self->get_key(SILENT, BLOCKING));
    } until (exists($mapping->{$key}) || $key eq chr(3) || !$self->is_connected());
    if ($key eq chr(3)) {
        $command = 'DISCONNECT';
    } else {
        $id      = $mapping->{$key}->{'id'};
        $command = $mapping->{$key}->{'command'};
        $url     = $mapping->{$key}->{'url'};
        $text    = $mapping->{$key}->{'text'};
    } ## end else [ if ($key eq chr(3)) ]
    if ($self->is_connected() && $command ne 'DISCONNECT' && $command ne 'BACK') {
        $self->output($self->news_title_colorize($text));
        my $rss_string = `curl -s $url`;
        my $rss;
        my $list;
        eval {
            $rss = XML::RSS::LibXML->new;
            $rss->parse($rss_string);
            $list = $rss->items;
        };

        if ($@) {
            $self->{'debug'}->ERROR([$@]);
            $self->output("ERROR > $@");
        } else {
            my $text;
            foreach my $item (@{$list}) {
                last unless ($self->is_connected());
                if ($mode eq 'ANSI') {
                    $text .= '[% NAVY %]' . '━' x $self->{'USER'}->{'max_columns'} . "[% RESET %]\n";
                    $text .= '[% BRIGHT WHITE %][% B_TEAL %]       Title [% RESET %] [% GREEN %]' . $self->html_to_text($item->{'title'}) . "[% RESET %]\n";
                    $text .= '[% BRIGHT WHITE %][% B_TEAL %] Description [% RESET %] ' . $self->html_to_text($item->{'description'}) . "\n";
                    $text .= '[% BRIGHT WHITE %][% B_TEAL %]        Link [% RESET %] [% YELLOW %]' . $item->{'link'} . "[% RESET %]\n";
                } elsif ($mode eq 'PETSCII') {
                    $text .= '[% YELLOW %]       Title [% RESET %] [% GREEN %]' . $self->html_to_text($item->{'title'}) . "\n";
                    $text .= '[% YELLOW %] Description [% RESET %] ' . $self->html_to_text($item->{'description'}) . "\n";
                    $text .= '[% YELLOW %]        Link [% RESET %] [% YELLOW %]' . $item->{'link'} . "[% RESET %]\n";

lib/BBS/Universal.pm  view on Meta::CPAN

    my $heading  = '';          #  = "\t";
    my $counter  = $sections;

    for (my $count = $sections - 1; $count > 0; $count--) {
        $heading .= ' NAME                         VERSION ';
        if ($count) {
            $heading .= "\t";
        } else {
            $heading .= "\n";
        }
    } ## end for (my $count = $sections...)
    $heading = '[% BRIGHT YELLOW %][% B_RED %]' . $heading . '[% RESET %]';
    foreach my $v (sort(keys %{ $self->{'VERSIONS'} })) {
        next if ($bbs_only && $v !~ /^BBS/);
        $versions .= sprintf(' %-28s  %.03f', $v, $self->{'VERSIONS'}->{$v});
        $counter--;
        if ($counter <= 1) {
            $counter = $sections;
            $versions .= "\n";
        } else {
            $versions .= "\t";
        }
    } ## end foreach my $v (sort(keys %{...}))
    chop($versions) if (substr($versions, -1, 1) eq "\t");
    $self->{'debug'}->DEBUG(['End SysOp Versions Format']);
    return ($heading . $versions . "\n");
} ## end sub sysop_versions_format

sub sysop_disk_free {    # Show the Disk Free portion of Statistics
    my $self = shift;

    $self->{'debug'}->DEBUG(['Start SysOp Disk Free']);
    my $diskfree = '';
    if ((-e '/usr/bin/duf' || -e '/usr/local/bin/duf') && $self->configuration('USE DUF') =~ /^(TRUE|YES|OM)$/) {
        my ($wsize, $hsize, $wpixels, $hpixels) = GetTerminalSize();
        $diskfree = `duf -theme ansi -width $wsize`;
    } else {
        my @free  = split(/\n/, `nice df -h -T`);    # Get human readable disk free showing type
        my $width = 1;
        foreach my $l (@free) {
            $width = max(length($l), $width);        # find the width of the widest line
        }
        foreach my $line (@free) {
            next if ($line =~ /tmp|boot/);
            if ($line =~ /^Filesystem/) {
                $diskfree .= '[% B_BLUE %][% BRIGHT YELLOW %]' . " $line " . ' ' x ($width - length($line)) . "[% RESET %]\n";    # Make the heading the right width
            } else {
                $diskfree .= " $line\n";
            }
        } ## end foreach my $line (@free)
    } ## end else [ if ((-e '/usr/bin/duf'...))]
    $self->{'debug'}->DEBUG(['End SysOp Disk Free']);
    return ($diskfree);
} ## end sub sysop_disk_free

sub sysop_load_menu {
    my $self = shift;
    my $row  = shift;
    my $file = shift;

    $self->{'debug'}->DEBUG(['Start SysOp Load Menu', "  SysOp Load Menu $file"]);
    my $mapping = { 'TEXT' => '' };
    my $mode    = 1;
    my $text    = locate($row, 1) . cldown;
    open(my $FILE, '<', $file);

    shift(@{ $self->{'sysop_menu_files'} });
    push(@{ $self->{'sysop_menu_files'} }, $file);
    for (my $count = 0; $count < 5; $count++) {
        if ($count == 4) {
            print locate(($count + 1), 108), colored(['green', 'on_black'], clline . $self->{'sysop_menu_files'}->[$count]);
        } else {
            print locate(($count + 1), 108), colored(['ansi22', 'on_black'], clline . $self->{'sysop_menu_files'}->[$count]);
        }
    } ## end for (my $count = 0; $count...)
    while (chomp(my $line = <$FILE>)) {
        next if ($line =~ /^\#/);
        if ($mode) {
            if ($line !~ /^---/) {
                my ($k, $cmd, $color, $t) = split(/\|/, $line);
                $k   = uc($k);
                $cmd = uc($cmd);
                $self->{'debug'}->DEBUGMAX([$k, $cmd, $color, $t]);
                $mapping->{$k} = {
                    'command' => $cmd,
                    'color'   => $color,
                    'text'    => $t,
                };
            } else {
                $mode = 0;
            }
        } else {
            $mapping->{'TEXT'} .= $self->sysop_detokenize($line) . "\n";
        }
    } ## end while (chomp(my $line = <$FILE>...))
    close($FILE);
    $self->{'debug'}->DEBUG(['End SysOp Load Menu']);
    return ($mapping);
} ## end sub sysop_load_menu

sub sysop_pager {
    my $self   = shift;
    my $text   = shift;
    my $offset = (scalar(@_)) ? shift : 0;

    $self->{'debug'}->DEBUG(['Start SysOp Pager']);
    my ($wsize, $hsize, $wpixels, $hpixels) = GetTerminalSize();
    my @lines;
    @lines = split(/\n$/, $text);
    my $size = ($hsize - ($self->{'CACHE'}->get('START_ROW') + $self->{'CACHE'}->get('ROW_ADJUST')));
    $size -= $offset;
    my $scroll = TRUE;
    my $count  = 1;

    while (scalar(@lines)) {
        my $line = shift(@lines);
        $self->sysop_output("$line\n");

        #        $self->sysop_ansi_output("$line\n");
        $count++;
        if ($count >= $size) {
            $count  = 1;
            $scroll = $self->sysop_scroll();
            last unless ($scroll);
        }
    } ## end while (scalar(@lines))
    $self->{'debug'}->DEBUG(['End SysOp Pager']);
    return ($scroll);
} ## end sub sysop_pager

sub sysop_parse_menu {
    my $self = shift;
    my $row  = shift;
    my $file = shift;

    my $row     = $self->{'CACHE'}->get('START_ROW') + $self->{'CACHE'}->get('ROW_ADJUST');
    my $animate = ($self->{'CONF'}->{'SYSOP ANIMATED MENU'}) ? TRUE : FALSE;
    $self->{'debug'}->DEBUG(['Start SysOp Parse Menu', "  SysOp Parse Menu $file"]);
    my $mapping = $self->sysop_load_menu($row, $file);
    print locate($row, 1), cldown;
    my $scroll = $self->sysop_pager($mapping->{'TEXT'}, 3);
    my $keys   = '';
    print "\r", cldown unless ($scroll);
    $self->sysop_show_choices($mapping);
    $self->sysop_prompt('Choose');
    my $key;
    do {
        $key = uc($self->sysop_keypress($row, $animate));
        threads->yield();
    } until (exists($mapping->{$key}));
    print $mapping->{$key}->{'command'}, "\n";
    $self->{'debug'}->DEBUG(['End SysOp Parse Menu']);
    return ($mapping->{$key}->{'command'});
} ## end sub sysop_parse_menu

sub sysop_decision {
    my $self = shift;
    $self->{'debug'}->DEBUG(['Start SysOp Decision']);
    my $response;
    do {
        $response = uc($self->sysop_keypress());
    } until ($response =~ /Y|N/i || $response eq chr(13));
    if ($response eq 'Y') {
        print "YES\n";
        $self->{'debug'}->DEBUG(['  SysOp Decision YES']);
        $self->{'debug'}->DEBUG(['End SysOp Decision']);
        return (TRUE);
    } ## end if ($response eq 'Y')
    $self->{'debug'}->DEBUG(['  SysOp Decision NO']);
    print "NO\n";
    $self->{'debug'}->DEBUG(['End SysOp Decision']);
    return (FALSE);
} ## end sub sysop_decision

sub sysop_keypress {
    my $self = shift;
    my $row;
    my $animate = FALSE;
    if (scalar(@_)) {
        $row     = shift;
        $animate = shift;
    }

    my $key;
    do {
        $self->{'CACHE'}->set('SHOW_STATUS', FALSE);
        ReadMode 'ultra-raw';
        $key = ReadKey(0.25);
        ReadMode 'restore';
        $self->sysop_animate($row) if ($animate);
        threads->yield();
        $self->{'CACHE'}->set('SHOW_STATUS', TRUE);
    } until (defined($key));
    return ($key);
} ## end sub sysop_keypress

sub sysop_animate {
    my $self = shift;
    my $row  = shift;

    my @color = @{ $self->{'sysop_menu_colors'} };

    my $text = "\e[s" . "\e[1;91H\e[48;2;0;0;96m\e[93m " . $self->clock() . " \e[" . $row++ . ";1H\e[" . $color[0] . "mâ—¥\e[" . ($color[0] + 10) . "m \e[0m\e[" . $color[0] . "m\e[7mâ—¥\e[0m" . "\e[" . $row++ . ";2H\e[" . $color[1] . "mâ—¥\e[" . ($co...

    $self->{'CACHE'}->set('SHOW_STATUS', FALSE);
    print $text;
    $self->{'CACHE'}->set('SHOW_STATUS', TRUE);

    my $l = pop(@color);
    unshift(@color, $l);
    $self->{'sysop_menu_colors'} = \@color;
} ## end sub sysop_animate

lib/BBS/Universal.pm  view on Meta::CPAN

        } elsif ($conf eq 'DEFAULT TEXT MODE') {
            $c .= ' - ANSI, ASCII, ATASCII, PETSCII';
        }
        if ($view) {
            $table->row($conf, $c);
        } else {
            if ($conf =~ /AUTHOR/) {
                $table->row(' ', $conf, $c);
            } else {
                $table->row($choice, $conf, $c);
                $count++;
            }
        } ## end else [ if ($view) ]
    } ## end foreach my $conf (sort(keys...))
    my $output = $table->thick('RED')->draw();
    foreach my $change ('AUTHOR EMAIL', 'AUTHOR LOCATION', 'AUTHOR NAME', 'DATABASE USERNAME', 'DATABASE NAME', 'DATABASE PORT', 'DATABASE TYPE', 'DATBASE USERNAME', 'DATABASE HOSTNAME', '300, 600, 1200, 2400, 4800, 9600, 19200, FULL', '%d = day, %m ...
        if ($output =~ /$change/) {
            my $ch;
            if (/^(AUTHOR|DATABASE)/) {
                $ch = '[% YELLOW %]' . $change . '[% RESET %]';
            } else {
                $ch = '[% GRAY 11 %]' . $change . '[% RESET %]';
            }
            $output =~ s/$change/$ch/gs;
        } ## end if ($output =~ /$change/)
    } ## end foreach my $change ('AUTHOR EMAIL'...)
    {
        my $ch = colored(['cyan'], 'CHOICE');
        $output =~ s/CHOICE/$ch/gs;
        $ch = colored(['bright_yellow'], 'STATIC NAME');
        $output =~ s/STATIC NAME/$ch/gs;
        $ch = colored(['bright_yellow'], 'STATIC VALUE');
        $output =~ s/STATIC VALUE/$ch/gs;
        $ch = colored(['green'], 'CONFIG NAME');
        $output =~ s/CONFIG NAME/$ch/gs;
        $ch = colored(['cyan'], 'CONFIG VALUE');
        $output =~ s/CONFIG VALUE/$ch/gs;
        $ch = colored(['green'], 'TRUE');
        $output =~ s/TRUE/$ch/gs;
        $ch = colored(['red'], 'FALSE');
        $output =~ s/FALSE/$ch/gs;
        $ch = colored(['green'], 'ON');
        $output =~ s/ ON / $ch /gs;
        $ch = colored(['red'], 'OFF');
        $output =~ s/ OFF / $ch /gs;
        $ch = colored(['green'], 'YES');
        $output =~ s/YES/$ch/gs;
        $ch = colored(['red'], 'NO');
        $output =~ s/ NO / $ch /gs;
    }
    my $response;
    if ("$view" eq 'string') {
        $response = $output;
    } elsif ($view == TRUE) {
        print $self->sysop_detokenize($output);
        print 'Press a key to continue ... ';
        $response = $self->sysop_keypress();
    } elsif ($view == FALSE) {
        print $self->sysop_detokenize($output);
        print $self->sysop_menu_choice('TOP',    '',    '');
        print $self->sysop_menu_choice('Z',      'RED', 'Return to Settings Menu');
        print $self->sysop_menu_choice('BOTTOM', '',    '');
        $self->sysop_prompt('Choose');
        $response = TRUE;
    } ## end elsif ($view == FALSE)
    $self->{'debug'}->DEBUG(['End SysOp View Configuration']);
    return ($response);
} ## end sub sysop_view_configuration

sub sysop_edit_configuration {
    my $self = shift;

    $self->{'debug'}->DEBUG(['Start SysOp Edit Configuration']);
    $self->sysop_view_configuration(FALSE);
    my $types = {
        'BBS NAME'            => { 'max' => 50, 'type' => STRING, },
        'BBS ROOT'            => { 'max' => 60, 'type' => STRING, },
        'HOST'                => { 'max' => 20, 'type' => HOST, },
        'THREAD MULTIPLIER'   => { 'max' => 2,  'type' => NUMERIC, },
        'PORT'                => { 'max' => 5,  'type' => NUMERIC, },
        'DEFAULT BAUD RATE'   => { 'max' => 5,  'type' => RADIO, 'choices' => ['300', '600', '1200', '2400', '4800', '9600', '19200', '38400', '57600', '115200', 'FULL'], },
        'DEFAULT TEXT MODE'   => { 'max' => 7,  'type' => RADIO, 'choices' => ['ANSI', 'ASCII', 'ATASCII', 'PETSCII'], },
        'DEFAULT TIMEOUT'     => { 'max' => 3,  'type' => NUMERIC, },
        'FILES PATH'          => { 'max' => 60, 'type' => STRING, },
        'LOGIN TRIES'         => { 'max' => 1,  'type' => NUMERIC, },
        'MEMCACHED HOST'      => { 'max' => 20, 'type' => HOST, },
        'MEMCACHED NAMESPACE' => { 'max' => 32, 'type' => STRING, },
        'MEMCACHED PORT'      => { 'max' => 5,  'type' => NUMERIC, },
        'DATE FORMAT'         => { 'max' => 14, 'type' => RADIO,   'choices' => ['MONTH/DAY/YEAR', 'DAY/MONTH/YEAR', 'YEAR/MONTH/DAY',], },
        'SYSOP ANIMATED MENU' => { 'max' => 5,  'type' => BOOLEAN, 'choices' => ['ON', 'OFF'], },
        'USE DUF'             => { 'max' => 5,  'type' => BOOLEAN, 'choices' => ['ON', 'OFF'], },
        'PLAY SYSOP SOUNDS'   => { 'max' => 5,  'type' => BOOLEAN, 'choices' => ['ON', 'OFF'], },
    };
    my $choice;
    do {
        $choice = uc($self->sysop_keypress());
    } until ($choice =~ /[A-R]|Z/i);
    if ($choice =~ /Z/i) {
        print "BACK\n";
        return (FALSE);
    }

    $choice = ("$choice" =~ /[A-Y]/i) ? $choice = (ord($choice) - 65) : $choice;
    my @conf = grep(!/STATIC|AUTHOR/, sort(keys %{ $self->{'CONF'} }));
    if ($types->{ $conf[$choice] }->{'type'} == RADIO || $types->{ $conf[$choice] }->{'type'} == BOOLEAN) {
        print '(Edit) ', $conf[$choice], ' (' . join(' ', @{ $types->{ $conf[$choice] }->{'choices'} }) . ') ', charnames::string_vianame('BLACK RIGHT-POINTING TRIANGLE'), '  ';
    } else {
        print '(Edit) ', $conf[$choice], ' ', charnames::string_vianame('BLACK RIGHT-POINTING TRIANGLE'), '  ';
    }
    my $string;
    $self->{'debug'}->DEBUGMAX([$self->configuration()]);
    $string = $self->sysop_get_line($types->{ $conf[$choice] }, $self->configuration($conf[$choice]));
    my $response = TRUE;
    if ($string eq '') {
        $response = FALSE;
    } else {
        $self->configuration($conf[$choice], $string);
    }
    $self->{'debug'}->DEBUG(['End SysOp Edit Configuration']);
    return ($response);
} ## end sub sysop_edit_configuration

lib/BBS/Universal.pm  view on Meta::CPAN

    } elsif ($name eq 'text_mode' && $val !~ /^(ASCII|ATASCII|PETSCII|ANSI)$/) {
        print locate($row, ($column + $size)), colored(['red'], ' Only ASCII,ATASCII,PETSCII,ANSI'), locate($row, $column);
        $response = FALSE;
    } elsif ($name =~ /(prefer_nickname|_files|_message|sysop)/ && $val !~ /^(yes|no|true|false|on|off|0|1)$/i) {
        print locate($row, ($column + $size)), colored(['red'], ' Only Yes/No or On/Off or 1/0'), locate($row, $column);
        $response = FALSE;
    } elsif ($name eq 'birthday' && $val ne '' && $val !~ /(\d\d\d\d)-(\d\d)-(\d\d)/) {
        print locate($row, ($column + $size)), colored(['red'], ' YEAR-MM-DD'), locate($row, $column);
        $self->{'debug'}->DEBUG(['sysop_validate_fields end']);
        $response = FALSE;
    }
    $self->{'debug'}->DEBUG(['Start SysOp Validate Fields']);
    return ($response);
} ## end sub sysop_validate_fields

sub sysop_prompt {
    my $self = shift;
    my $text = shift;

    $self->{'debug'}->DEBUG(['Start SysOp Prompt']);
    my $response = "\n" . '[% B_BRIGHT MAGENTA %][% BLACK %] SYSOP TOOL [% RESET %] ' . $text . ' [% PINK %][% BLACK RIGHTWARDS ARROWHEAD %][% RESET %] ';
    print $self->sysop_detokenize($response);
    $self->{'debug'}->DEBUG(['End SysOp Prompt']);
    return (TRUE);
} ## end sub sysop_prompt

sub sysop_detokenize {
    my $self = shift;
    my $text = shift;

    # OPERATION TOKENS
    foreach my $key (keys %{ $self->{'sysop_tokens'} }) {
        my $ch = '';
        if ($key eq 'MIDDLE VERTICAL RULE color' && $text =~ /\[\%\s+MIDDLE VERTICAL RULE (.*?)\s+\%\]/) {
            my $color = $1;
            if (ref($self->{'sysop_tokens'}->{$key}) eq 'CODE') {
                $ch = $self->{'sysop_tokens'}->{$key}->($self, $color);
            }
            $text =~ s/\[\%\s+MIDDLE VERTICAL RULE (.*?)\s+\%\]/$ch/gi;
        } elsif ($text =~ /\[\%\s+$key\s+\%\]/) {
            if (ref($self->{'sysop_tokens'}->{$key}) eq 'CODE') {
                $ch = $self->{'sysop_tokens'}->{$key}->($self);
            } else {
                $ch = $self->{'sysop_tokens'}->{$key};
            }
            $text =~ s/\[\%\s+$key\s+\%\]/$ch/gi;
        } ## end elsif ($text =~ /\[\%\s+$key\s+\%\]/)
    } ## end foreach my $key (keys %{ $self...})

    $text = $self->ansi_decode($text);

    return ($text);
} ## end sub sysop_detokenize

sub sysop_menu_choice {
    my $self   = shift;
    my $choice = shift;
    my $color  = shift;
    my $desc   = shift;

    $self->{'debug'}->DEBUG(['Start SysOp Menu Choice']);
    my $response;
    if ($choice eq 'TOP') {
        $response = charnames::string_vianame('BOX DRAWINGS LIGHT ARC DOWN AND RIGHT') . charnames::string_vianame('BOX DRAWINGS LIGHT HORIZONTAL') . charnames::string_vianame('BOX DRAWINGS LIGHT ARC DOWN AND LEFT') . "\n";
    } elsif ($choice eq 'BOTTOM') {
        $response = $self->news_title_colorize(charnames::string_vianame('BOX DRAWINGS LIGHT ARC UP AND RIGHT') . charnames::string_vianame('BOX DRAWINGS LIGHT HORIZONTAL') . charnames::string_vianame('BOX DRAWINGS LIGHT ARC UP AND LEFT')) . "\n";
    } else {
        $response = $self->ansi_decode(charnames::string_vianame('BOX DRAWINGS LIGHT VERTICAL') . '[% BOLD %][% ' . $color . ' %]' . $choice . '[% RESET %]' . charnames::string_vianame('BOX DRAWINGS LIGHT VERTICAL') . ' [% ' . $color . ' %]' . charna...
    }
    $self->{'debug'}->DEBUG(['End SysOp Menu Choice']);
    return ($response);
} ## end sub sysop_menu_choice

sub sysop_showenv {
    my $self = shift;

    $self->{'debug'}->DEBUG(['Start SysOp ShowENV']);
    my $MAX  = 0;
    my $text = '';
    foreach my $e (keys %ENV) {
        $MAX = max(length($e), $MAX);
    }

    foreach my $env (sort(keys %ENV)) {
        if ($ENV{$env} =~ /\n/g || $env eq 'WHATISMYIP_INFO') {
            my @in     = split(/\n/, $ENV{$env});
            my $indent = $MAX + 4;
            $text .= '[% BRIGHT WHITE %]' . sprintf("%${MAX}s", $env) . "[% RESET %] = ---\n";
            foreach my $line (@in) {
                if ($line =~ /\:/) {
                    my ($f, $l) = $line =~ /^(.*?):(.*)/;
                    chomp($l);
                    chomp($f);
                    $f = uc($f);
                    if ($f eq 'IP') {
                        $l = colored(['bright_green'], $l);
                        $f = 'IP ADDRESS';
                    }
                    my $le = 11 - length($f);
                    $f .= ' ' x $le;
                    $l = colored(['green'],    uc($l))                                                                         if ($l =~ /^ok/i);
                    $l = colored(['bold red'], 'U') . colored(['bold bright_white'], 'S') . colored(['bold bright_blue'], 'A') if ($l =~ /^us/i);
                    $text .= colored(['bold bright_cyan'], sprintf("%${indent}s", $f)) . " = $l\n";
                } else {
                    $text .= "$line\n";
                }
            } ## end foreach my $line (@in)
        } else {
            my $orig = $ENV{$env};
            my $new;

            if ($orig =~ /(256color)/) {
                $new = colored(['red'], '2') . colored(['green'], '5') . colored(['yellow'], '6') . colored(['cyan'], 'c') . colored(['bright_blue'], 'o') . colored(['magenta'], 'l') . colored(['bright_green'], 'o') . colored(['bright_blue'], 'r');
                $orig =~ s/$1/$new/g;
            } elsif ($orig =~ /(truecolor)/) {
                $new = colored(['red'], 't') . colored(['green'], 'r') . colored(['yellow'], 'u') . colored(['cyan'], 'e') . colored(['bright_blue'], 'c') . colored(['magenta'], 'o') . colored(['bright_green'], 'l') . colored(['bright_blue'], 'o') . ...
                $orig =~ s/$1/$new/g;
            } elsif ($orig =~ /(\d+\.\d+\.\d+\.\d+)/) {
                $new = '[% BRIGHT GREEN %]' . $1 . '[% RESET %]';
                $orig =~ s/$1/$new/g;
            } elsif ($orig =~ /(ubuntu)/i) {
                $new = '[% ORANGE %]' . $1 . '[% RESET %]';
                $orig =~ s/$1/$new/g;
            } elsif ($orig =~ /(redhat)/i) {
                $new = colored(['bright_red'], $1);
                $orig =~ s/$1/$new/g;
            } elsif ($orig =~ /(fedora)/i) {
                $new = colored(['bright_cyan'], $1);
                $orig =~ s/$1/$new/g;
            } elsif ($orig =~ /(mint)/i) {



( run in 1.969 second using v1.01-cache-2.11-cpan-cdf2f3d4e48 )