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