--- ChivanetModcon/modcon 2025/01/30 19:16:06 1.1 +++ ChivanetModcon/modcon 2025/02/04 18:55:12 1.6 @@ -4,145 +4,625 @@ # ChivaNet Moderator Console # Copyright (C) 2025 Coherent Logic Development LLC # -# $Id: modcon,v 1.1 2025/01/30 19:16:06 snw Exp $ +# $Id: modcon,v 1.6 2025/02/04 18:55:12 snw Exp $ # # Author: Serena Willis # # Licensed AGPL-3.0 # # $Log: modcon,v $ -# Revision 1.1 2025/01/30 19:16:06 snw -# Initial revision +# 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 Getopt::Long; use Data::Dumper; use Term::ReadKey; +use Getopt::Long; +use Text::Glob qw(match_glob); -my $rasurl = ''; +my $sso_account = ''; my $apikey = ''; my $online = 0; my $modcon_version = '0.0.1'; my $cnclient = ''; -my $rasclient = ''; - 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 $ct = 0; - + $cnclient->GET("/chivanet/users"); my $json = $cnclient->responseContent(); my $hashref = decode_json($json); if($hashref->{ok} == 1) { my $arrayref = $hashref->{users}; - my @users = sort(@{$arrayref}); - - foreach my $user (@users) { - print "$user\n"; - $ct = $ct + 1; - - if($ct > $hchar - 2) { - print "ENTER to continue, Q to quit..."; - my $resp = ; - chomp($resp); - if($resp eq "Q") { - return; - } - elsif($resp eq "q") { - return; - } - $ct = 0; - } - } + 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 = "[ChivaNet Support]: $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 $mode = 'SSO'; - my $user = '---'; - + my $pmode = $mode; + my @path = (); + while (1) { - print "MODCON [$mode/$user]> "; + $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 = ; chomp($line); $rawcmd = $line; my @cmd = split(' ', $rawcmd); - if ($cmd[0] eq "quit") { + 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 "mode") { - if($cmd[1] eq "SSO") { - $mode = "SSO"; - $user = "---"; + 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[1] eq "RAS") { - $mode = "RAS"; - $user = "---"; + } + 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 "?\n"; + print "trace: invalid command outside of ras directory\n"; } } - elsif ($cmd[0] eq "list") { - if($cmd[1] eq "users") { - if($mode eq "SSO") { - list_sso_users(); + elsif ($cmd[0] eq "field") { + if($mode eq "SSO") { + if($user ne "---") { + my $act = $account->{account}; + print "$user\-\>$cmd[1]: $act->{$cmd[1]}\n"; } - elsif($mode eq "RAS") { - list_ras_users(); + else { + print "field: no user selected\n"; } } else { - print "?\n"; + 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 "?\n" + print "$cmd[0]: command not found\n"; } } } sub main { - GetOptions("rasurl=s" => \$rasurl, - "apikey=s" => \$apikey) - or die("error in command line arguments"); + ($wchar, $hchar, $wpixels, $hpixels) = GetTerminalSize(); - ($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://chivanet.org/rest/api', + host => "https://$site/rest/api", timeout => 10}); - - $cnclient->addHeader('Authorization', "Apikey $apikey"); - - $rasclient = REST::Client->new({ - host => $rasurl, - timeout => 10}); print "ChivaNet MODCON $modcon_version\n"; print " Copyright (C) 2025 Coherent Logic Development LLC\n\n"; + + print "username: "; + $sso_account = ; + chomp($sso_account); + + print "password: "; + ReadMode('noecho'); + my $password = ; + chomp($password); + ReadMode('normal'); + - prompt(); + 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" }