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