Annotation of ChivanetModcon/modcon, revision 1.6

1.1       snw         1: #!/usr/bin/env perl
                      2: 
                      3: #
                      4: # ChivaNet Moderator Console
                      5: #  Copyright (C) 2025 Coherent Logic Development LLC
                      6: #
1.6     ! snw         7: # $Id: modcon,v 1.5 2025/02/01 03:17:46 snw Exp $
1.1       snw         8: #
                      9: # Author: Serena Willis <snw@coherent-logic.com>
                     10: #
                     11: # Licensed AGPL-3.0
                     12: #
1.2       snw        13: # $Log: modcon,v $
1.6     ! snw        14: # Revision 1.5  2025/02/01 03:17:46  snw
        !            15: # Fix session list
        !            16: #
1.5       snw        17: # Revision 1.4  2025/01/31 19:41:00  snw
                     18: # Move to a UNIX UI paradigm
                     19: #
1.4       snw        20: # Revision 1.3  2025/01/31 15:39:06  snw
                     21: # Minor fixes
                     22: #
1.3       snw        23: # Revision 1.2  2025/01/31 13:38:51  snw
                     24: # Initial basic functions working
                     25: #
1.2       snw        26: # Revision 1.1.1.1  2025/01/30 19:16:06  snw
                     27: # Initial commit
                     28: #
1.1       snw        29: #
                     30: 
                     31: use REST::Client;
                     32: use JSON;
                     33: use Data::Dumper;
                     34: use Term::ReadKey;
1.4       snw        35: use Getopt::Long;
1.6     ! snw        36: use Text::Glob qw(match_glob);
1.1       snw        37: 
1.4       snw        38: my $sso_account = '';
1.1       snw        39: my $apikey = '';
                     40: my $online = 0;
                     41: my $modcon_version = '0.0.1';
                     42: my $cnclient = '';
                     43: my $wchar = '';
                     44: my $hchar = '';
                     45: my $wpixels = '';
                     46: my $hpixels = '';
1.2       snw        47: my $account = '';
1.4       snw        48: my $mode = '---';
                     49: my $user = '---';
                     50: $site = '';
                     51: 
                     52: sub list_directories {
                     53:     return ('sso', 'ras');
                     54: }
1.2       snw        55: 
1.1       snw        56: sub list_sso_users {
1.4       snw        57:     $cnclient->GET("/chivanet/users");       
1.1       snw        58:     my $json = $cnclient->responseContent();
                     59:     my $hashref = decode_json($json);
                     60: 
                     61:     if($hashref->{ok} == 1) {
                     62:         my $arrayref = $hashref->{users};
1.4       snw        63:         my @users = @{$arrayref};
                     64: 
                     65:         return @users;
1.1       snw        66:     }
                     67:     else {
                     68:         print "RPC error\n";
1.4       snw        69:         return ();
1.1       snw        70:     }
                     71: }
                     72: 
                     73: 
1.2       snw        74: sub select_sso_user {
                     75:     my($id) = @_;
                     76: 
                     77:     $cnclient->GET("/chivanet/validate_user?id=$id");
                     78:     my $json = $cnclient->responseContent();
                     79:     my $hashref = decode_json($json);
                     80:     
                     81:     if($hashref->{exists} == 1) {
                     82: 
                     83:         $cnclient->GET("/chivanet/user?id=$id");
                     84:         my $json = $cnclient->responseContent();
                     85:         $account = decode_json($json);
1.4       snw        86: 
1.2       snw        87:         return $id;
                     88:     }
                     89:     else {
                     90:         return "---";
1.3       snw        91:     }            
1.2       snw        92: }
                     93: 
                     94: sub select_ras_user {
                     95:     my($id) = @_;
                     96: 
                     97:     $cnclient->GET("/chivanet/validate_sn?id=$id");
                     98:     my $json = $cnclient->responseContent();
                     99:     my $hashref = decode_json($json);
                    100: 
                    101:     if($hashref->{exists} == 1) {
                    102:         return $id;
                    103:     }
                    104:     else {
                    105:         return "---";
                    106:     }
                    107: }
                    108: 
                    109: sub trace_ras_sn {
                    110:     my($user) = @_;
                    111:     
                    112:     $cnclient->GET("/chivanet/trace_sn?id=$user");
                    113:     my $json = $cnclient->responseContent();
                    114:     my $hashref = decode_json($json);
                    115:     my $record = $hashref->{account};
                    116:     my $result = $record->{id};
                    117:     
                    118:     print "RAS screen name $user belongs to SSO account $result; switching to SSO mode\n";
1.3       snw       119:     return $result;   
1.2       snw       120: }
                    121: 
                    122: sub list_ras_sessions {
                    123:     $cnclient->GET("/chivanet/ras_sessions");
                    124:     my $json = $cnclient->responseContent();
                    125:     my $hashref = decode_json($json);
                    126:     my $sessions = $hashref->{sessions};
                    127:     my $arrayref = $sessions->{sessions};
1.5       snw       128:     my @result = ();
1.2       snw       129:     
                    130:     foreach my $session (@{$arrayref}) {
1.5       snw       131:         push(@result, $session->{id});
1.2       snw       132:     }
                    133:     
1.5       snw       134:     return @result;
1.2       snw       135: }
                    136: 
                    137: sub list_ras_screennames {
                    138:     my($id) = @_;
1.5       snw       139: 
                    140:     my @result = ();
                    141:     $cnclient->GET("/chivanet/user_ras_screen_names?id=$id");
1.2       snw       142:     my $json = $cnclient->responseContent();
                    143:     my $hashref = decode_json($json);
                    144:     my $arrayref = $hashref->{screen_names};
1.5       snw       145: 
                    146:     foreach my $item (@{$arrayref}) {
                    147:         push(@result, $item->{screen_name});
                    148:     }
                    149: 
1.2       snw       150:     
1.5       snw       151:     return @result;   
1.4       snw       152: }
1.2       snw       153: 
1.4       snw       154: sub list_ras_users {
1.3       snw       155: 
1.5       snw       156:     my @result = ();
1.4       snw       157:     
1.5       snw       158:     $cnclient->GET("/chivanet/all_ras_screen_names");
                    159:     my $json = $cnclient->responseContent();
                    160:     my $hashref = decode_json($json);
                    161: 
                    162:     if($hashref->{ok} == 0) {
                    163:         print "RPC error\n";
                    164:         return @result;
                    165:     }
                    166:     
                    167:     my $arrayref = $hashref->{screen_names};
                    168: 
                    169:     foreach my $entryref (@{$arrayref}) {
                    170:         push(@result, $entryref->{id});
                    171:     }
                    172: 
                    173:     return @result;    
1.2       snw       174: }
                    175: 
                    176: sub print_sso_user {
                    177:     my $act = $account->{account};
                    178: 
                    179:     print "\n";
                    180:     print "Username          :  $act->{id}\n";
                    181:     print "Real Name         :  $act->{last_name}, $act->{first_name}\n";
                    182:     print "Display Name      :  $act->{display_name}\n";
                    183:     print "Pronouns          :  $act->{pronouns}\n";
                    184:     print "Profile Image     :  https://chivanet.org$act->{profile_photo}\n";        
                    185:     print "E-Mail Address    :  $act->{email}\n";
                    186:     print "Permission Level  :  $act->{perm_level}\n";
                    187:     print "Created           :  $act->{create_ts}\n";
                    188:     print "Banned            :  $act->{mod_banned}\n\n";
                    189: }
                    190: 
                    191: sub print_ras_user {
                    192:     my($id) = @_;
                    193: 
                    194:     $cnclient->GET("/chivanet/sn_status?id=$id");
                    195:     my $json = $cnclient->responseContent();
                    196:     my $hashref = decode_json($json);
                    197:     my $status = $hashref->{status};
                    198:     my $online = "offline";
                    199:     
                    200:     if($status->{online} == 1) {        
                    201:         $online = "online";        
                    202:     }
                    203:     
                    204:     print "\n";
                    205: 
                    206: 
                    207:     if($online eq "online") {
                    208:         my $sess = $status->{session};
                    209:         my $service = 'AIM';
                    210:         
                    211:         if($sess->{is_icq} == 1) {
                    212:             $service = 'ICQ';
                    213:         }
                    214: 
                    215:         if($sess->{idle_seconds} > 0) {
                    216:             $online = "idle";
                    217:         }
                    218:         
                    219:         print "Screen Name       :  $id [$online]\n";
                    220:         print "Service           :  $service\n";
                    221:         if($online eq "online") {
                    222:             print "Time Online (secs):  $sess->{online_seconds}\n";
                    223:         }
                    224:         else {
                    225:             print "Time Idle (secs)  :  $sess->{idle_seconds}\n";
                    226:         }
                    227:         print "Away Message      :  $sess->{away_message}\n\n";
                    228:     }
                    229:     else {
                    230:         print "Screen Name       :  $id [$online]\n\n";
1.5       snw       231:     }    
1.2       snw       232:     
                    233: }
                    234: 
                    235: sub send_im {
                    236:     my($user, $msgbody) = @_;
                    237: 
                    238:     print "sending message \"$msgbody\" to $user...";   
                    239:     
                    240:     my $msg = "<font color=red><strong>[ChivaNet Support]:</strong></font> $msgbody";
                    241:     
                    242:     $cnclient->GET("/chivanet/send_im?from=ChivaNet&to=$user&message=$msg");
                    243:     my $json = $cnclient->responseContent();
                    244:     my $hashref = decode_json($json);
                    245:     
                    246:     if($hashref->{status} == 1) {
                    247:         print "[OK]\n";
                    248:     }
                    249:     else {
                    250:         print "[FAIL]\n";
                    251:     }
                    252: }
                    253: 
1.5       snw       254: sub ls {
1.6     ! snw       255:     my ($description, $match, @entries) = @_;
        !           256: 
        !           257:     if($match eq "") {
        !           258:         $match = '*';
        !           259:     }
1.5       snw       260: 
1.6     ! snw       261:     print "Directory of [$description] matching \'$match\':\n\n";
        !           262:         
1.5       snw       263:     my @sorted = sort(@entries);
                    264:     my $maxlen = 0;
1.6     ! snw       265:     
1.5       snw       266:     foreach my $entry (@sorted) {
                    267:         my $len = length($entry);
                    268:         if ($len > $maxlen) {
                    269:             $maxlen = $len;
                    270:         }
                    271:     }
1.6     ! snw       272: 
1.5       snw       273:     $maxlen = $maxlen + 2;
1.6     ! snw       274: 
1.5       snw       275:     my $ct = $#sorted + 1;
                    276:     foreach my $entry (@sorted) {
1.6     ! snw       277:         if(match_glob($match, $entry)) {
        !           278:             if($col + $maxlen >= $wchar) {
        !           279:                 print "$entry\n";
        !           280:                 $col = 0;
        !           281:             }
        !           282:             else {
        !           283:                 printf("%-$maxlen\s", $entry);
        !           284:                 $col = $col + $maxlen;
        !           285:             }
1.5       snw       286:         }
                    287:         else {
1.6     ! snw       288:             $ct = $ct - 1;
        !           289:         }
        !           290:     }
1.5       snw       291:     
1.6     ! snw       292:     print "\n\n$ct matching items in directory\n";
1.5       snw       293: }
                    294: 
1.1       snw       295: sub prompt {
                    296:     
                    297:     my $rawcmd = '';
1.4       snw       298:     my $pmode = $mode;    
                    299:     my @path = ();
                    300:     
1.1       snw       301:     while (1) {
1.4       snw       302:         $pmode = lc $mode;
                    303:         my $prompt = '';
                    304:         if($pmode ne "---") {
                    305:             if($user eq '---') {
                    306:                 $prompt = "$sso_account\@$site:[/$pmode]\$ ";
                    307:             }
                    308:             else {
                    309:                 $prompt = "$sso_account\@$site:[/$pmode/$user]\$ ";
                    310:             }
                    311:         }
                    312:         else {
                    313:             $prompt = "$sso_account\@$site:[/]\$ ";
                    314:         }
                    315:         print $prompt;
1.1       snw       316:         my $line = <STDIN>;
                    317:         chomp($line);
                    318:         $rawcmd = $line;
                    319:         
                    320:         my @cmd = split(' ', $rawcmd);
                    321:         
1.5       snw       322:         if ($cmd[0] eq "exit" || $cmd[0] eq "logout" || $cmd[0] eq "quit" || $cmd[0] eq "bye" || $cmd[0] eq "lo") {
1.1       snw       323:             return;
                    324:         }
1.4       snw       325:         elsif ($cmd[0] eq "pwd") {
                    326:             my $pstr = join('/', @path);
                    327:             print "/$pstr\n";        
                    328:         }        
                    329:         elsif ($cmd[0] eq "cd") {
                    330:             my $abspath = false;
                    331: 
                    332:             if($cmd[1] eq "..") {
                    333:                 if(@path) {
                    334:                     if($#path == 1) {
                    335:                         $user = "---";
                    336:                         @path = ($pmode);
                    337:                     }
                    338:                     elsif($#path == 0) {
                    339:                         $mode = "---";
                    340:                         @path = ();
                    341:                     }
                    342:                 }
                    343:                 else {
                    344:                     print "already in root directory\n";
                    345:                 }
1.2       snw       346:             }
                    347:             else {
1.4       snw       348:                 my @oldpath = @path;
                    349:                 @path = split('/', $cmd[1]);
                    350:             
                    351:                 if(substr($cmd[1], 0, 1) eq '/') {
                    352:                     $abspath = true;
                    353:                     shift @path;                
                    354:                 }
                    355:                 
                    356:                 if($abspath eq true || !@oldpath) {
                    357:                     if($#path == 0) {
                    358:                         # mode, no user
                    359:                         if($path[0] eq "sso") {
                    360:                             $mode = "SSO";
                    361:                             $user = "---";
                    362:                             @path = ('sso');
                    363:                         }
                    364:                         elsif($path[0] eq "ras") {
                    365:                             $mode = "RAS";
                    366:                             $user = "---";
                    367:                             @path = ('ras');
                    368:                         }
                    369:                         else {
                    370:                             print "$path[0]:  no such directory exists\n";
                    371:                             @path = @oldpath;
                    372:                         }
                    373:                     }
                    374:                     elsif($#path == 1) {
                    375:                         # mode and user
                    376:                         if($path[0] eq "sso") {
                    377:                             $user = select_sso_user($path[1]);                          
                    378:                             if($user eq "---") {
                    379:                                 print "$path[1]:  no such file exists in $pmode\n";
                    380:                                 @path = @oldpath;
                    381:                             }
                    382:                             else {
                    383:                                 $mode = "SSO";
                    384:                                 @path = ('sso', $user);
                    385:                             }
                    386:                         }
                    387:                         elsif($path[0] eq "ras") {
                    388:                             $user = select_ras_user($path[1]);
                    389:                             if($user eq "---") {
                    390:                                 print "$path[1]:  no such file exists in $pmode\n";
                    391:                                 @path = @oldpath;
                    392:                             }
                    393:                             else {
                    394:                                 $mode = "RAS";
                    395:                                 @path = ('ras', $user);
                    396:                             }
                    397:                         }
                    398:                         else {
                    399:                             print "$path[0]:  no such directory exists\n";
                    400:                             @path = @oldpath;
                    401:                         }
                    402:                     }
                    403:                     else {
                    404:                         if($cmd[1] eq '/') {
                    405:                             $mode = '---';
                    406:                             $user = '---';
                    407:                             @path = ();
                    408:                         }
                    409:                         else {
                    410:                             print "cd:  invalid path specification\n";
                    411:                             @path = @oldpath;
                    412:                         }
                    413:                     }
1.2       snw       414:                 }
                    415:                 else {
1.4       snw       416:                     if($#oldpath == 1) {
                    417:                         print "invalid path specification\n";
                    418:                         @path = @oldpath;
                    419:                     }
                    420:                     else {
                    421:                         if($#path == 0) {
                    422:                             if($pmode eq "sso") {
                    423:                                 $user = select_sso_user($path[0]);                          
                    424:                                 if($user eq "---") {
                    425:                                     print "$path[0]:  no such file exists in $pmode\n";
                    426:                                     @path = @oldpath;
                    427:                                 }
                    428:                                 else {
                    429:                                     $mode = "SSO";
                    430:                                     @path = ('sso', $user);
                    431:                                 }
                    432:                             }
                    433:                             elsif($pmode eq "ras") {
                    434:                                 $user = select_ras_user($path[0]);                          
                    435:                                 if($user eq "---") {
                    436:                                     print "$path[0]:  no such file exists in $pmode\n";
                    437:                                     @path = @oldpath;
                    438:                                 }
                    439:                                 else {
1.6     ! snw       440:                                     $mode = "RAS";
        !           441:                                     @path = ('ras', $user);
1.4       snw       442:                                 }
                    443:                             }
                    444:                             else {
                    445:                                 print "$path[0]:  no such directory exists\n";
                    446:                                 @path = @oldpath;                            
                    447:                             }
                    448:                         }
                    449:                         else {
                    450:                             print "invalid path specification\n";
                    451:                             @path = @oldpath;
                    452:                         }
                    453:                     }
                    454:                 }                                         
1.5       snw       455:             } # if ..                                  
1.2       snw       456:         }
                    457:         elsif ($cmd[0] eq "im") {
                    458:             if($mode eq "RAS") {
                    459:                 if($user ne "---") {
                    460:                     my @msga = @cmd[1..$#cmd];
                    461:                     my $msgbody = join(' ', @msga);                    
                    462:                     send_im($user, $msgbody);
                    463:                 }
                    464:                 else {
1.4       snw       465:                     print "im:  no user selected\n";
1.2       snw       466:                 }
                    467:             }
                    468:             else {
1.4       snw       469:                 print "im:  invalid command outside of ras directory\n";
1.2       snw       470:             }
                    471:         }    
                    472:         elsif ($cmd[0] eq "trace") {
                    473:             if($mode eq "RAS") {
                    474:                 if($user ne "---") {                    
                    475:                     my $id = trace_ras_sn($user);
                    476:                     $mode = "SSO";
                    477:                     $user = select_sso_user($id);
                    478:                     print_sso_user;
                    479:                 }
                    480:                 else {
1.4       snw       481:                     print "trace:  no user select\n";
1.2       snw       482:                 }
                    483:             }
                    484:             else {
1.4       snw       485:                 print "trace:  invalid command outside of ras directory\n";
1.2       snw       486:             }
                    487:         }
                    488:         elsif ($cmd[0] eq "field") {
1.4       snw       489:             if($mode eq "SSO") {
                    490:                 if($user ne "---") {
                    491:                     my $act = $account->{account};
                    492:                     print "$user\-\>$cmd[1]:  $act->{$cmd[1]}\n";
                    493:                 }
                    494:                 else {
                    495:                     print "field:  no user selected\n";
                    496:                 }
1.2       snw       497:             }
                    498:             else {
1.4       snw       499:                 print "field:  command invalid outside of ras directory\n";
1.2       snw       500:             }
                    501:         }
1.5       snw       502:         elsif ($cmd[0] eq "sessions") {
                    503:             if($mode ne "RAS") {
                    504:                 print "sessions:  must be in ras mode\n";
                    505:             }
                    506:             else {
1.6     ! snw       507:                 if($cmd[1]) {
        !           508:                     ls "active RAS sessions", $cmd[1], list_ras_sessions();
        !           509:                 }
        !           510:                 else {
        !           511:                     ls "active RAS sessions", '*', list_ras_sessions();
        !           512:                 }
1.5       snw       513:             }
                    514:         }
                    515:         elsif ($cmd[0] eq "lssn") {
                    516:             if($mode ne "SSO" || $user eq "---") {
                    517:                 print "lssn:  must be in sso mode with a user selected\n";
                    518:             }
                    519:             else {
                    520:                 my @sns = list_ras_screennames($user);
1.6     ! snw       521:                 if($cmd[1]) {
        !           522:                     ls "RAS screen names for $user", $match, @sns;
        !           523:                 }
        !           524:                 else {
        !           525:                     ls "RAS screen names for $user", '*', @sns;
        !           526:                 }
1.5       snw       527:             }                    
                    528:         }
1.4       snw       529:         elsif ($cmd[0] eq "ls") {
                    530:             my @entries = ();
                    531:             
                    532:             if($mode eq "---") {
                    533:                 @entries = list_directories();
1.1       snw       534:             }
                    535:             else {
1.4       snw       536:                 if($user ne "---") {
                    537:                     if($mode eq "SSO") {
                    538:                         print_sso_user;
                    539:                     }
                    540:                     elsif($mode eq "RAS") {
                    541:                         print_ras_user($user);
                    542:                     }                                
1.2       snw       543:                 }
                    544:                 else {
1.4       snw       545:                     if($mode eq "SSO") {
                    546:                         @entries = list_sso_users();
1.2       snw       547:                     }
                    548:                     else {
1.4       snw       549:                         @entries = list_ras_users();
1.2       snw       550:                     }
                    551:                 }
1.4       snw       552:             }
                    553: 
                    554:             if(@entries) {
                    555:                 my $col = 0;
                    556:                 my $pstr = join('/', @path);
                    557:                 my $pfin = "/$pstr";
1.6     ! snw       558:                 if($cmd[1]) {
        !           559:                     ls $pfin, $cmd[1], @entries;
        !           560:                 }
        !           561:                 else {
        !           562:                     ls $pfin, '*', @entries;
        !           563:                 }
1.2       snw       564:             }
1.4       snw       565: 
1.1       snw       566:         }
                    567:         else {
1.4       snw       568:             print "$cmd[0]: command not found\n";
1.1       snw       569:         }
                    570:     }
                    571: 
                    572: }
                    573: 
                    574: sub main {  
1.4       snw       575:     ($wchar, $hchar, $wpixels, $hpixels) = GetTerminalSize();    
1.1       snw       576: 
1.4       snw       577:     GetOptions("site=s" => \$site) or die "error in command line arguments";
                    578:     
                    579:     if($site eq "") {
                    580:         print "modcon: must supply -site command-line argument\n";
                    581:         return;
                    582:     }
1.1       snw       583:     
                    584:     $cnclient = REST::Client->new({
1.4       snw       585:         host => "https://$site/rest/api",
1.1       snw       586:         timeout => 10});
                    587:     
                    588:     print "ChivaNet MODCON $modcon_version\n";
                    589:     print " Copyright (C) 2025 Coherent Logic Development LLC\n\n";
1.4       snw       590: 
                    591:     print "username: ";
                    592:     $sso_account = <STDIN>;
                    593:     chomp($sso_account);
                    594: 
                    595:     print "password: ";
                    596:     ReadMode('noecho');
                    597:     my $password = <STDIN>;
                    598:     chomp($password);
                    599:     ReadMode('normal');
                    600: 
                    601:     
                    602:     my $params = $cnclient->buildQuery([username => $sso_account, password => $password]);
                    603:     my $result = $cnclient->POST("/chivanet/modcon_auth", substr($params, 1), {'Content-type' => 'application/x-www-form-urlencoded'});
                    604:     my $http_response = $result->{_res};
                    605:     my $json = $http_response->{_content};
                    606:     my $apiresult = decode_json($json);
1.1       snw       607:     
1.4       snw       608:     if($apiresult->{ok} == 1) {
                    609:         $cnclient->addHeader('Authorization', "Apikey $apiresult->{token}");
                    610: 
                    611:         select_sso_user $sso_account;
                    612:         my $act = $account->{account};
                    613:         
                    614:         print "\n\nWelcome to MODCON, $act->{display_name}!\n\n";
                    615:         
                    616:         prompt();
                    617:     }
                    618:     else {
                    619:         print "\nerror: $apiresult->{error}\n";
                    620:         return;
                    621:     }
                    622: 
1.1       snw       623:     print "Goodbye.\n"
                    624: }
                    625: 
                    626: main();

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