#!/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>