File:  [Coherent Logic Development] / ChivanetModcon / modcon
Revision 1.8: download - view: text, annotated - select for diffs
Mon Mar 10 15:34:45 2025 UTC (3 weeks, 2 days ago) by snw
Branches: MAIN
CVS tags: HEAD
Undo the change

#!/usr/bin/env perl

#
# ChivaNet Moderator Console
#  Copyright (C) 2025 Coherent Logic Development LLC
#
# $Id: modcon,v 1.8 2025/03/10 15:34:45 snw Exp $
#
# Author: Serena Willis <snw@coherent-logic.com>
#
# Licensed AGPL-3.0
#
# $Log: modcon,v $
# Revision 1.8  2025/03/10 15:34:45  snw
# Undo the change
#
# Revision 1.7  2025/03/10 15:31:54  snw
# Test
#
# Revision 1.6  2025/02/04 18:55:12  snw
# Updates
#
# Revision 1.5  2025/02/01 03:17:46  snw
# Fix session list
#
# Revision 1.4  2025/01/31 19:41:00  snw
# Move to a UNIX UI paradigm
#
# Revision 1.3  2025/01/31 15:39:06  snw
# Minor fixes
#
# Revision 1.2  2025/01/31 13:38:51  snw
# Initial basic functions working
#
# Revision 1.1.1.1  2025/01/30 19:16:06  snw
# Initial commit
#
#

use REST::Client;
use JSON;
use Data::Dumper;
use Term::ReadKey;
use Getopt::Long;
use Text::Glob qw(match_glob);

my $sso_account = '';
my $apikey = '';
my $online = 0;
my $modcon_version = '0.0.1';
my $cnclient = '';
my $wchar = '';
my $hchar = '';
my $wpixels = '';
my $hpixels = '';
my $account = '';
my $mode = '---';
my $user = '---';
$site = '';

sub list_directories {
    return ('sso', 'ras');
}

sub list_sso_users {
    $cnclient->GET("/chivanet/users");       
    my $json = $cnclient->responseContent();
    my $hashref = decode_json($json);

    if($hashref->{ok} == 1) {
        my $arrayref = $hashref->{users};
        my @users = @{$arrayref};

        return @users;
    }
    else {
        print "RPC error\n";
        return ();
    }
}


sub select_sso_user {
    my($id) = @_;

    $cnclient->GET("/chivanet/validate_user?id=$id");
    my $json = $cnclient->responseContent();
    my $hashref = decode_json($json);
    
    if($hashref->{exists} == 1) {

        $cnclient->GET("/chivanet/user?id=$id");
        my $json = $cnclient->responseContent();
        $account = decode_json($json);

        return $id;
    }
    else {
        return "---";
    }            
}

sub select_ras_user {
    my($id) = @_;

    $cnclient->GET("/chivanet/validate_sn?id=$id");
    my $json = $cnclient->responseContent();
    my $hashref = decode_json($json);

    if($hashref->{exists} == 1) {
        return $id;
    }
    else {
        return "---";
    }
}

sub trace_ras_sn {
    my($user) = @_;
    
    $cnclient->GET("/chivanet/trace_sn?id=$user");
    my $json = $cnclient->responseContent();
    my $hashref = decode_json($json);
    my $record = $hashref->{account};
    my $result = $record->{id};
    
    print "RAS screen name $user belongs to SSO account $result; switching to SSO mode\n";
    return $result;   
}

sub list_ras_sessions {
    $cnclient->GET("/chivanet/ras_sessions");
    my $json = $cnclient->responseContent();
    my $hashref = decode_json($json);
    my $sessions = $hashref->{sessions};
    my $arrayref = $sessions->{sessions};
    my @result = ();
    
    foreach my $session (@{$arrayref}) {
        push(@result, $session->{id});
    }
    
    return @result;
}

sub list_ras_screennames {
    my($id) = @_;

    my @result = ();
    $cnclient->GET("/chivanet/user_ras_screen_names?id=$id");
    my $json = $cnclient->responseContent();
    my $hashref = decode_json($json);
    my $arrayref = $hashref->{screen_names};

    foreach my $item (@{$arrayref}) {
        push(@result, $item->{screen_name});
    }

    
    return @result;   
}

sub list_ras_users {

    my @result = ();
    
    $cnclient->GET("/chivanet/all_ras_screen_names");
    my $json = $cnclient->responseContent();
    my $hashref = decode_json($json);

    if($hashref->{ok} == 0) {
        print "RPC error\n";
        return @result;
    }
    
    my $arrayref = $hashref->{screen_names};

    foreach my $entryref (@{$arrayref}) {
        push(@result, $entryref->{id});
    }

    return @result;    
}

sub print_sso_user {
    my $act = $account->{account};

    print "\n";
    print "Username          :  $act->{id}\n";
    print "Real Name         :  $act->{last_name}, $act->{first_name}\n";
    print "Display Name      :  $act->{display_name}\n";
    print "Pronouns          :  $act->{pronouns}\n";
    print "Profile Image     :  https://chivanet.org$act->{profile_photo}\n";        
    print "E-Mail Address    :  $act->{email}\n";
    print "Permission Level  :  $act->{perm_level}\n";
    print "Created           :  $act->{create_ts}\n";
    print "Banned            :  $act->{mod_banned}\n\n";
}

sub print_ras_user {
    my($id) = @_;

    $cnclient->GET("/chivanet/sn_status?id=$id");
    my $json = $cnclient->responseContent();
    my $hashref = decode_json($json);
    my $status = $hashref->{status};
    my $online = "offline";
    
    if($status->{online} == 1) {        
        $online = "online";        
    }
    
    print "\n";


    if($online eq "online") {
        my $sess = $status->{session};
        my $service = 'AIM';
        
        if($sess->{is_icq} == 1) {
            $service = 'ICQ';
        }

        if($sess->{idle_seconds} > 0) {
            $online = "idle";
        }
        
        print "Screen Name       :  $id [$online]\n";
        print "Service           :  $service\n";
        if($online eq "online") {
            print "Time Online (secs):  $sess->{online_seconds}\n";
        }
        else {
            print "Time Idle (secs)  :  $sess->{idle_seconds}\n";
        }
        print "Away Message      :  $sess->{away_message}\n\n";
    }
    else {
        print "Screen Name       :  $id [$online]\n\n";
    }    
    
}

sub send_im {
    my($user, $msgbody) = @_;

    print "sending message \"$msgbody\" to $user...";   
    
    my $msg = "<font color=red><strong>[ChivaNet Support]:</strong></font> $msgbody";
    
    $cnclient->GET("/chivanet/send_im?from=ChivaNet&to=$user&message=$msg");
    my $json = $cnclient->responseContent();
    my $hashref = decode_json($json);
    
    if($hashref->{status} == 1) {
        print "[OK]\n";
    }
    else {
        print "[FAIL]\n";
    }
}

sub ls {
    my ($description, $match, @entries) = @_;

    if($match eq "") {
        $match = '*';
    }

    print "Directory of [$description] matching \'$match\':\n\n";
        
    my @sorted = sort(@entries);
    my $maxlen = 0;
    
    foreach my $entry (@sorted) {
        my $len = length($entry);
        if ($len > $maxlen) {
            $maxlen = $len;
        }
    }

    $maxlen = $maxlen + 2;

    my $ct = $#sorted + 1;
    foreach my $entry (@sorted) {
        if(match_glob($match, $entry)) {
            if($col + $maxlen >= $wchar) {
                print "$entry\n";
                $col = 0;
            }
            else {
                printf("%-$maxlen\s", $entry);
                $col = $col + $maxlen;
            }
        }
        else {
            $ct = $ct - 1;
        }
    }
    
    print "\n\n$ct matching items in directory\n";
}

sub prompt {
    
    my $rawcmd = '';
    my $pmode = $mode;    
    my @path = ();
    
    while (1) {
        $pmode = lc $mode;
        my $prompt = '';
        if($pmode ne "---") {
            if($user eq '---') {
                $prompt = "$sso_account\@$site:[/$pmode]\$ ";
            }
            else {
                $prompt = "$sso_account\@$site:[/$pmode/$user]\$ ";
            }
        }
        else {
            $prompt = "$sso_account\@$site:[/]\$ ";
        }
        print $prompt;
        my $line = <STDIN>;
        chomp($line);
        $rawcmd = $line;
        
        my @cmd = split(' ', $rawcmd);
        
        if ($cmd[0] eq "exit" || $cmd[0] eq "logout" || $cmd[0] eq "quit" || $cmd[0] eq "bye" || $cmd[0] eq "lo") {
            return;
        }
        elsif ($cmd[0] eq "pwd") {
            my $pstr = join('/', @path);
            print "/$pstr\n";        
        }        
        elsif ($cmd[0] eq "cd") {
            my $abspath = false;

            if($cmd[1] eq "..") {
                if(@path) {
                    if($#path == 1) {
                        $user = "---";
                        @path = ($pmode);
                    }
                    elsif($#path == 0) {
                        $mode = "---";
                        @path = ();
                    }
                }
                else {
                    print "already in root directory\n";
                }
            }
            else {
                my @oldpath = @path;
                @path = split('/', $cmd[1]);
            
                if(substr($cmd[1], 0, 1) eq '/') {
                    $abspath = true;
                    shift @path;                
                }
                
                if($abspath eq true || !@oldpath) {
                    if($#path == 0) {
                        # mode, no user
                        if($path[0] eq "sso") {
                            $mode = "SSO";
                            $user = "---";
                            @path = ('sso');
                        }
                        elsif($path[0] eq "ras") {
                            $mode = "RAS";
                            $user = "---";
                            @path = ('ras');
                        }
                        else {
                            print "$path[0]:  no such directory exists\n";
                            @path = @oldpath;
                        }
                    }
                    elsif($#path == 1) {
                        # mode and user
                        if($path[0] eq "sso") {
                            $user = select_sso_user($path[1]);                          
                            if($user eq "---") {
                                print "$path[1]:  no such file exists in $pmode\n";
                                @path = @oldpath;
                            }
                            else {
                                $mode = "SSO";
                                @path = ('sso', $user);
                            }
                        }
                        elsif($path[0] eq "ras") {
                            $user = select_ras_user($path[1]);
                            if($user eq "---") {
                                print "$path[1]:  no such file exists in $pmode\n";
                                @path = @oldpath;
                            }
                            else {
                                $mode = "RAS";
                                @path = ('ras', $user);
                            }
                        }
                        else {
                            print "$path[0]:  no such directory exists\n";
                            @path = @oldpath;
                        }
                    }
                    else {
                        if($cmd[1] eq '/') {
                            $mode = '---';
                            $user = '---';
                            @path = ();
                        }
                        else {
                            print "cd:  invalid path specification\n";
                            @path = @oldpath;
                        }
                    }
                }
                else {
                    if($#oldpath == 1) {
                        print "invalid path specification\n";
                        @path = @oldpath;
                    }
                    else {
                        if($#path == 0) {
                            if($pmode eq "sso") {
                                $user = select_sso_user($path[0]);                          
                                if($user eq "---") {
                                    print "$path[0]:  no such file exists in $pmode\n";
                                    @path = @oldpath;
                                }
                                else {
                                    $mode = "SSO";
                                    @path = ('sso', $user);
                                }
                            }
                            elsif($pmode eq "ras") {
                                $user = select_ras_user($path[0]);                          
                                if($user eq "---") {
                                    print "$path[0]:  no such file exists in $pmode\n";
                                    @path = @oldpath;
                                }
                                else {
                                    $mode = "RAS";
                                    @path = ('ras', $user);
                                }
                            }
                            else {
                                print "$path[0]:  no such directory exists\n";
                                @path = @oldpath;                            
                            }
                        }
                        else {
                            print "invalid path specification\n";
                            @path = @oldpath;
                        }
                    }
                }                                         
            } # if ..                                  
        }
        elsif ($cmd[0] eq "im") {
            if($mode eq "RAS") {
                if($user ne "---") {
                    my @msga = @cmd[1..$#cmd];
                    my $msgbody = join(' ', @msga);                    
                    send_im($user, $msgbody);
                }
                else {
                    print "im:  no user selected\n";
                }
            }
            else {
                print "im:  invalid command outside of ras directory\n";
            }
        }    
        elsif ($cmd[0] eq "trace") {
            if($mode eq "RAS") {
                if($user ne "---") {                    
                    my $id = trace_ras_sn($user);
                    $mode = "SSO";
                    $user = select_sso_user($id);
                    print_sso_user;
                }
                else {
                    print "trace:  no user select\n";
                }
            }
            else {
                print "trace:  invalid command outside of ras directory\n";
            }
        }
        elsif ($cmd[0] eq "field") {
            if($mode eq "SSO") {
                if($user ne "---") {
                    my $act = $account->{account};
                    print "$user\-\>$cmd[1]:  $act->{$cmd[1]}\n";
                }
                else {
                    print "field:  no user selected\n";
                }
            }
            else {
                print "field:  command invalid outside of ras directory\n";
            }
        }
        elsif ($cmd[0] eq "sessions") {
            if($mode ne "RAS") {
                print "sessions:  must be in ras mode\n";
            }
            else {
                if($cmd[1]) {
                    ls "active RAS sessions", $cmd[1], list_ras_sessions();
                }
                else {
                    ls "active RAS sessions", '*', list_ras_sessions();
                }
            }
        }
        elsif ($cmd[0] eq "lssn") {
            if($mode ne "SSO" || $user eq "---") {
                print "lssn:  must be in sso mode with a user selected\n";
            }
            else {
                my @sns = list_ras_screennames($user);
                if($cmd[1]) {
                    ls "RAS screen names for $user", $match, @sns;
                }
                else {
                    ls "RAS screen names for $user", '*', @sns;
                }
            }                    
        }
        elsif ($cmd[0] eq "ls") {
            my @entries = ();
            
            if($mode eq "---") {
                @entries = list_directories();
            }
            else {
                if($user ne "---") {
                    if($mode eq "SSO") {
                        print_sso_user;
                    }
                    elsif($mode eq "RAS") {
                        print_ras_user($user);
                    }                                
                }
                else {
                    if($mode eq "SSO") {
                        @entries = list_sso_users();
                    }
                    else {
                        @entries = list_ras_users();
                    }
                }
            }

            if(@entries) {
                my $col = 0;
                my $pstr = join('/', @path);
                my $pfin = "/$pstr";
                if($cmd[1]) {
                    ls $pfin, $cmd[1], @entries;
                }
                else {
                    ls $pfin, '*', @entries;
                }
            }

        }
        else {
            print "$cmd[0]: command not found\n";
        }
    }

}

sub main {  
    ($wchar, $hchar, $wpixels, $hpixels) = GetTerminalSize();    

    GetOptions("site=s" => \$site) or die "error in command line arguments";
    
    if($site eq "") {
        print "modcon: must supply -site command-line argument\n";
        return;
    }
    
    $cnclient = REST::Client->new({
        host => "https://$site/rest/api",
        timeout => 10});
    
    print "ChivaNet MODCON $modcon_version\n";
    print " Copyright (C) 2025 Coherent Logic Development LLC\n\n";

    print "username: ";
    $sso_account = <STDIN>;
    chomp($sso_account);

    print "password: ";
    ReadMode('noecho');
    my $password = <STDIN>;
    chomp($password);
    ReadMode('normal');

    
    my $params = $cnclient->buildQuery([username => $sso_account, password => $password]);
    my $result = $cnclient->POST("/chivanet/modcon_auth", substr($params, 1), {'Content-type' => 'application/x-www-form-urlencoded'});
    my $http_response = $result->{_res};
    my $json = $http_response->{_content};
    my $apiresult = decode_json($json);
    
    if($apiresult->{ok} == 1) {
        $cnclient->addHeader('Authorization', "Apikey $apiresult->{token}");

        select_sso_user $sso_account;
        my $act = $account->{account};
        
        print "\n\nWelcome to MODCON, $act->{display_name}!\n\n";
        
        prompt();
    }
    else {
        print "\nerror: $apiresult->{error}\n";
        return;
    }

    print "Goodbye.\n"
}

main();

FreeBSD-CVSweb <freebsd-cvsweb@FreeBSD.org>