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