Annotation of ChivanetModcon/modcon, revision 1.5

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

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