#!/usr/bin/env perl # # ChivaNet Moderator Console # Copyright (C) 2025 Coherent Logic Development LLC # # $Id: modcon,v 1.4 2025/01/31 19:41:00 snw Exp $ # # Author: Serena Willis # # Licensed AGPL-3.0 # # $Log: modcon,v $ # 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; 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 $ct = 0; foreach my $session (@{$arrayref}) { print "$session->{screen_name}\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; } } print "$sessions->{count} current sessions\n"; } sub list_ras_screennames { my($id) = @_; $cnclient->GET("/chivanet/ras_screen_names?id=$id"); my $json = $cnclient->responseContent(); my $hashref = decode_json($json); my $arrayref = $hashref->{screen_names}; return @{$arrayref}; } sub list_ras_users { return (); } 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"; } # print Dumper($status); } 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 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 = ; 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") { 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 = "SSO"; @path = ('sso', $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 "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"; 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($col + $maxlen >= $wchar) { print "\n"; $col = 0; } else { printf("%-$maxlen\s", $entry); $col = $col + $maxlen; } } print "\n\t$ct items in $pfin\n"; } } 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 = ; chomp($sso_account); print "password: "; ReadMode('noecho'); my $password = ; 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();