· 9 years ago · Jan 31, 2017, 10:20 PM
1#!/usr/bin/env perl
2
3use strict;
4use warnings;
5
6use Term::EditLine qw(CC_EOF CC_REDISPLAY CC_ARGHACK CC_NEWLINE CC_REFRESH CC_NORM);
7use Text::ParseWords;
8use Term::ReadKey;
9use Term::ANSIColor qw( :constants );
10use File::Path qw(make_path);
11
12use DBI;
13
14###################
15# Globals #
16###################
17
18my(
19 $dbtoken, # Set in &dbselect() to determine if database has been selected; Holds the name selected db
20 $line, # Current line for Term::Editline ( $term->gets() )
21 @tokens, # Used for Text::ParseWords to tokenize $line for tab completion
22 $term, # Term::Editline object
23);
24
25# Hashes
26my(
27 %prompt, # Term::Editline prompt. Used in &cfg() to print location
28 %cmd, # Main command options
29 %helpcmd, # Help command locations
30 %cfg, # Sets terminal dimentions mostly used to determine if "|less" is used or not
31 %helprnt, # Holds the print text for &help() menu; Displays commands/info E.g tab completion etc
32 %progprnt, # Holds the print text for &help() menu; Displays specific program commands/info
33 %files, # Holds the location of files used in this program
34 %dbcmd, # Database command locations
35 %notes, # Used in ¬es() to store found files for tab print/edit
36 %database # Used to store information pulled from database
37);
38
39# Colors
40
41my ($b, $r, $g, $y, $bl, $c, $w, $m, $bd) = (BLACK, RED, GREEN, YELLOW, BLUE, CYAN, WHITE, MAGENTA,BOLD);
42my $rt = RESET;
43my ($prompt) = "$w".'['."$c".'MikroTik'."$w".'-'."$g".'MGR'."$w".']'."$c";
44#my ($b,$r,$g,$y,$bd) = (BLUE, RED, GREEN, YELLOW, BOLD);
45
46
47
48# switches
49my (
50 $dbset, # Set in &dbselect() used to allow/deny access to locations
51 $fileprnt, # Used to define if tab completion is green or not green = files
52 $peopleset, # Set in &people() to define if a person has been chosen for &dbsearch()
53 $helpset, # Used to define what help options to print E.g programs/commands set in &help()
54 $peopletoken, # Holds the name of selected person set in &people() to use in &dbsearch()
55 $compset, # Used navigate &complete() and select the correct location for tab completion
56);
57
58$dbset = 'NO';
59$compset = 'start';
60$peopleset = 'NO';
61
62######################
63# End of globals #
64######################
65
66$term = Term::EditLine->new('ircdb');
67&createdir();
68&init();
69
70
71
72# Initialize settings
73sub init() {
74 &con_size();
75
76 if( $compset eq 'start' ) {
77 $term->set_prompt( $prompt{ mainstart } );
78 } else {
79 $term->set_prompt( $prompt{ mainc } );
80 }
81
82 $term->add_fun( 'complete', 'desc', \&complete );
83 $term->parse('bind','-e');
84 $term->parse( 'bind', '\t', 'complete' );
85
86 $compset = 'main';
87 $fileprnt = 'NO';
88
89 &main();
90}
91
92# Main loop
93sub main() {
94 my( $run );
95 while ( defined( $line = $term->gets() ) ) {
96 $term->set_prompt( $prompt{ main } );
97 $term->history_enter($line);
98 chomp($line);
99 @tokens = shellwords($line);
100 $run = shift(@tokens) || next;
101
102 if ( exists $cmd{ $run } ) {
103 &{ $cmd{$run}{run} };
104 }
105
106 if( $line =~ /^(q|quit)$/ ){
107 exit;
108 }
109 }
110}
111
112# Ued to position printed text, currently only being used for &dblist()
113sub display_list(\@) {
114 my $rlist = shift || return '';
115
116 my ( $col_width, $rows, $cnt, $item, @out,);
117 $col_width = 0;
118 $cnt = scalar(@{$rlist}) || return 1;
119 $col_width = 1;
120
121 foreach $item (@{$rlist}) {
122 $col_width = length($item) if ($col_width < length($item));
123 }
124
125 $rows = int($cnt / int($cfg{con}{width} / ($col_width * 0.1 )));
126 $rows = 1 if (1 > $rows);
127 for ($item = 0; $cnt -1 >= $item; $item++) {
128
129 $out[($item % $rows)] .= (($cnt - $rows) < $item)
130 ? $rlist->[$item]
131 : $rlist->[$item]
132 . ' ' x ($col_width - length($rlist->[$item]))
133 . ' ';
134 }
135 return "\n".join("\n", @out);
136}
137
138# Get and set current terminal dimentions
139sub con_size {
140 ( $cfg{con}{width}, $cfg{con}{height},
141 $cfg{con}{xpix}, $cfg{con}{ypix} ) = GetTerminalSize;
142 SetTerminalSize( $cfg{con}{width}, $cfg{con}{height},
143 $cfg{con}{xpix}, $cfg{con}{ypix} );
144}
145
146# Select help menu
147sub help {
148 $term->set_prompt( $prompt{ ircdbhelp } );
149 $compset = 'help';
150
151 while( defined( $line = $term->gets() ) ) {
152
153 my( $token );
154 chomp($line);
155 $term->history_enter($line);
156
157 @tokens = shellwords($line);
158 $token = shift(@tokens) || next;
159
160 if( $line =~ /^(q|quit)$/ ){
161 return &init();
162 }
163
164 if ( exists $helpcmd{ $token } ) {
165 $helpset = $token;
166 &{ $helpcmd{$token}{run} };
167 }
168 }
169}
170
171# Print help for commands/programs
172sub hlpcmdprnt() {
173 print $bd;
174 if( $helpset eq "commands" ) {
175 printf("%-15s %5s", "Command", "Description$rt\n");
176 printf("%-15s %5s", '-'x13,'-'x33);
177 print "\n";
178 foreach my $i (sort keys %helprnt) {
179 my $e = @{ $helprnt{$i} }[0];
180 my $ee = @{ $helprnt{$i} }[1];
181
182 printf("%-25s %10s", "$g$e$rt","$y$ee$rt\n" );
183 }
184 print "\n";
185 } else {
186 if( $helpset eq "programs" ) {
187 printf("%-15s %5s", "Program", "Description$rt\n");
188 printf("%-15s %5s", '-'x13,'-'x33);
189 print "\n";
190 foreach my $i (sort keys %progprnt) {
191 my $e = @{ $progprnt{$i} }[0];
192 my $ee = @{ $progprnt{$i} }[1];
193
194 printf("%-25s %10s", "$g$e$rt","$y$ee$rt\n" );
195 }
196 print "\n";
197 }
198 }
199}
200
201# Select database menu
202sub database() {
203 $term->set_prompt( $prompt{ database } );
204 $compset = 'database';
205
206 if($dbset eq 'NO') {
207 print RED "You have no database selected", RESET, "\n";
208 }
209 if($dbset eq 'YES' && $peopleset eq 'YES') {
210 print GREEN "Database $bd$dbtoken$rt$g selected with user $bd$peopletoken$rt$g", RESET, "\n";
211 }
212 else {
213 if($dbset eq 'YES') {
214 print GREEN "Database $bd$dbtoken$rt$g selected", RESET, "\n";
215 }
216 }
217
218 while( defined( $line = $term->gets() ) ) {
219 $term->history_enter($line);
220 my( $token );
221 chomp($line);
222
223 @tokens = shellwords($line);
224 $token = shift(@tokens) || next;
225
226 if( $line =~ /^(q|quit)$/ ){
227 return &init();
228 }
229
230 if ( exists $dbcmd{ $token } ) {
231 &{ $dbcmd{$token}{run} };
232 }
233 }
234}
235
236# Print current databases found
237sub dblist() {
238 my ($filename, @found);
239 opendir(DIR, $files{db}{dir}) or die "Cannot open dir: $files{db}{dir} $!";
240 while(defined($filename = readdir(DIR))) {
241 next if $filename !~ /^#/;
242 push(@found, $filename);
243 close(DIR);
244 }
245
246 if (@found) {
247 print $bd;
248 printf("%-15s", "Databases found$rt\n");
249 printf("%-15s", '-'x16);
250 print GREEN &display_list(\@found) .RESET."\n";
251 } else {
252 print $r."No databases found$rt\n";
253 }
254}
255
256# Select database to use
257sub dbselect() {
258 $compset = 'dbselect';
259 $fileprnt = 'YES';
260
261 if($dbset eq 'YES') {
262 print GREEN "Database $bd$dbtoken$rt$g selected", RESET, "\n";
263 }
264
265 my ($filename, @dbfound);
266 opendir(DIR, $files{db}{dir}) or die "Cannot open dir: $files{db}{dir} $!";
267 while(defined($filename = readdir(DIR))) {
268 $term->set_prompt( $prompt{ dbselect } );
269
270 next if $filename !~ /^#/;
271 push(@dbfound, $filename);
272
273 $files{ dbselect } = \@dbfound;
274 close(DIR);
275 }
276
277 while( defined( $line = $term->gets() ) ) {
278 my( $token );
279 chomp($line);
280 $term->history_enter($line);
281 @tokens = shellwords($line);
282 $token = shift(@tokens) || next;
283
284 if( $line =~ /^(q|quit)$/ ){
285 print "Pizzle\n";
286 return &init();
287 }
288
289 foreach (@dbfound) {
290 if ($_ =~ /^$token$/ ) {
291 $dbtoken = $token;
292 print "$dbtoken\n";
293 $dbset = 'YES';
294 }
295 }
296 $fileprnt = 'NO';
297 return &database();
298 }
299}
300
301# Select a user to do database search on
302sub people() {
303 my($sth,$dbh,$stmt,@data,$r,$token,@peoplefound);
304
305 if($dbset eq 'NO') {
306 &database;
307 }
308
309 $term->set_prompt( $prompt{ people } );
310 $compset = 'people';
311
312 $dbh = DBI->connect("DBI:SQLite:$files{ rootdir }".'/db/'."$dbtoken") or return warn('WRN: cannot ope database: ' . $dbtoken . ' :' .$!);
313
314 $sth = $dbh->table_info('%','%','%');
315 $dbh->do($stmt) or return warn $DBI::errstr;
316 $sth->execute or return warn("Could not execute statement: " . $sth->errstr);
317
318 while ($r = $sth->fetchrow_hashref()) {
319 foreach my $i ($r->{TABLE_NAME}) {
320 next if $i =~ /sqlite_master|sqlite_temp_master/;
321 push (@peoplefound, $i);
322 }
323 $database{ people } = \@peoplefound;
324 }
325 @data = @peoplefound;
326
327 $sth->finish;
328 $dbh->disconnect();
329
330 while( defined( $line = $term->gets() ) ) {
331 my( $token );
332 chomp($line);
333 $term->history_enter($line);
334
335 @tokens = shellwords($line);
336 $token = shift(@tokens) || next;
337
338 if( $line =~ /^(q|quit)$/ ){
339 return &init();
340 }
341
342 foreach my $i (@data) {
343 if ($i =~ /^$token$/) {
344 $peopletoken = $token;
345 $peopleset = 'YES';
346 print "$peopleset\n";
347 }
348 }
349
350 return &database;
351 }
352}
353
354# Search database
355sub dbsearch() {
356 if ( $dbtoken ) {
357 my( $data,$dbh,$sth,$stmt,$name,$title,@data,$i,@datafound,$query,$table );
358 $dbh = DBI->connect("DBI:SQLite:$files{ rootdir }".'/db/'."$dbtoken") or return warn('WRN: cannot ope database: ' . $dbtoken . ' :' .$!);
359
360 if( $peopleset eq 'NO') {
361 print "What table you using?\n";
362 $table = <>;
363 chomp $table;
364 }
365
366 if( $peopleset eq 'NO' ) {
367 $query = "select * from `$table` where data like '%'||?||'%'";
368 } else {
369 $query = "select * from `$peopletoken` where data like '%'||?||'%'";
370 }
371 $sth = $dbh->prepare($query) or return warn('Could not prepare statement: ' .$dbh->errstr);
372 $dbh->do($stmt) or return warn $DBI::errstr;
373
374 print "Search $bd$dbtoken$rt database >: ";
375 while($name = <>) {
376 chomp $name;
377 $sth->execute($name) or return warn("Could not execute statement: " . $sth->errstr);
378
379 while (@data = $sth->fetchrow_array()) {
380 $data = $data[3];
381 push(@datafound,$data);
382 }
383
384 $dbh->disconnect();
385
386 #XXX Pipe breaks if you :q w/o scrolling less pipe to the bottom...XXX
387 if( scalar(@datafound) > $cfg{con}{height} ) {
388 open(PIPE, "|less -R") or die "No pipe $!";
389 foreach $i (@datafound) {
390 $i =~ s/$name/$r$name$g/gi;
391 print PIPE "$g$i$rt\n";
392 }
393 close(PIPE);
394 } else {
395 foreach $i (@datafound) {
396 $i =~ s/$name/$r$name$g/gi;
397 print "$g$i$rt\n";
398 }
399 }
400
401 if ($sth->rows == 0) {
402 print "$y No names matched $r$name$rt\n";
403 }
404 $sth->finish;
405
406 print "\n$r" . scalar($sth->rows) . " found$rt\n" ;
407 print "$y You just searched for $r$name $rt\n";
408 return &database;
409 }
410 } else {
411 print "$r"."You must select a database first".RESET."\n";
412 }
413}
414
415# Make / edit notes
416sub notes() {
417
418 my( $filename, @notefound );
419 $term->set_prompt( $prompt{ notes } );
420 $compset = 'notes';
421 $fileprnt = 'YES';
422
423 opendir(DIR, "$files{ rootdir }/notes") or die "Cannot open dir: $files{ rootdir }/notes $!";
424 while(defined($filename = readdir(DIR))) {
425 next if $filename !~ /.txt$/;
426 $filename =~ s/.txt$//;
427 push(@notefound, $filename);
428 close(DIR);
429 }
430 $notes{ notes } = \@notefound;
431
432 $fileprnt = 'YES';
433 while( defined( $line = $term->gets() ) ) {
434 $term->history_enter($line);
435 if( $line =~ /^(q|quit)$/ ){
436 $fileprnt = 'NO';
437 print "Pizzle\n";
438 return &init();
439 }
440
441 my( $token );
442 chomp($line);
443 @tokens = shellwords($line);
444 $token = shift(@tokens) || next;
445
446 foreach my $i (@notefound) {
447 if ($i =~ /^$token$/ ) {
448 system("vim $files{ notedir }/$token.txt"); return &init;
449 }
450 }
451 system("vim $files{ notedir }/$token.txt"); return &init;
452 }
453}
454
455# Route printed text to less, currently not being used
456sub lesspipe() {
457 my( @e, $in, @in, $fh, @a, $file );
458
459 @in = @{$_[0]};
460 foreach my $e (@{$_[0]}) {
461 @e = split("\n", $e);
462 foreach my $i (@e) {
463 push( @a, "$i\n" );
464 }
465 if( scalar(@a) > $cfg{con}{height} ) {
466 open(PIPE, "|less") or die "No pipe $!";
467 print PIPE "@a\n";
468 close(PIPE);
469 } else {
470 print "@a\n";
471 }
472 }
473}
474
475# Settings used throughout the program
476sub cfg() {
477 %files = (
478 rootdir => "$ENV{HOME}" . '/irclogs',
479 notedir => "$ENV{HOME}".'/irclogs/notes',
480
481 db => {
482 dir => "$ENV{HOME}".'/irclogs/db',
483 },
484 );
485
486 %database = (
487 people => {},
488 );
489
490 %prompt = (
491
492 mainc => "$prompt$w".' > '.RESET,
493 mainstart => 'Ircmgr (<TAB> for list): ',
494 main => 'Ircmgr: ',
495 dbselect => 'Dbselect: ',
496 ircdbhelp => 'Help: ',
497 notes => 'Notes: ',
498 database => 'Db: ',
499 people => 'People: ',
500 );
501
502 %cmd = (
503 notes => { run => \¬es, },
504 help => { run => \&help, },
505 database => { run => \&database, },
506 );
507
508 %helpcmd = (
509 commands => { run => \&hlpcmdprnt, },
510 programs => { run => \&hlpcmdprnt, },
511 info => { run => \&info, },
512 );
513
514 %dbcmd = (
515 search => { run => \&dbsearch, },
516 list => { run => \&dblist, },
517 dbselect => { run => \&dbselect, },
518 people => { run => \&people, },
519 );
520
521 %helprnt = (
522 quit => [ 'q or quit', 'Quit / Return to previous' ],
523 clear => [ '<CTRL>l', 'Clear screen' ],
524 autoccomplete => [ '<TAB>', 'Auto Complete / Show Options' ],
525 refreshlist => [ '<ENTER>', 'Refresh tab listing ( bug? )' ],
526 );
527
528 %progprnt = (
529 notes => ['notes <file>', "Notes command can take an argument E.g.$bd notes notes$rt$y creates a file in $bd$files{ notedir }\/notes.txt$rt" ],
530 );
531
532 %cfg = (
533 con => {
534 height => 0,
535 width => 0,
536 xpix => 0,
537 ypix => 0,
538 },
539 );
540}
541
542# Tab completion used for menus
543sub complete() {
544 my ( @list, @found, $fcnt, $token, $str, $curpos, $len, @char, $lth, $ii, $ok, $n_cmd, $pos, $substr, $sublen);
545
546 ( $str, $curpos, $len ) = $term->line;
547 ( $substr, undef ) = $str =~ /^\s*(\S+)(|\s+)/;
548 $substr = $substr || $str;
549 $sublen = length($substr);
550 $pos = index( $str, $substr ) + $sublen;
551
552 if( $compset eq 'main' ) {
553 foreach $token ( sort( keys(%cmd) ) ) {
554 if ( $token =~ /^\Q$substr\E/ ) {
555 next if ( ( length($token) ) == $sublen );
556 push( @found, $token );
557 }
558 push( @list, $token );
559 }
560 }
561
562 if( $compset eq 'help' ) {
563 foreach $token ( sort( keys(%helpcmd) ) ) {
564 if ( $token =~ /^\Q$substr\E/ ) {
565 next if ( ( length($token) ) == $sublen );
566 push( @found, $token );
567 }
568 push( @list, $token );
569 }
570 }
571
572 if( $compset eq 'database' ) {
573 foreach $token ( sort( keys(%dbcmd) ) ) {
574 if ( $token =~ /^\Q$substr\E/ ) {
575 next if ( ( length($token) ) == $sublen );
576 push( @found, $token );
577 }
578 push( @list, $token );
579 }
580 }
581 if( $compset eq 'dbselect' ) {
582 foreach $token (@{ $files{ dbselect } }) {
583 if ( $token =~ /^\Q$substr\E/ ) {
584 next if ( ( length($token) ) == $sublen );
585 push( @found, $token );
586 }
587 push( @list, $token );
588 }
589 }
590 if( $compset eq 'notes' ) {
591 foreach $token (@{ $notes{ notes } }) {
592 if ( $token =~ /^\Q$substr\E/ ) {
593 next if ( ( length($token) ) == $sublen );
594 push( @found, $token );
595 }
596 push( @list, $token );
597 }
598 }
599 if( $compset eq 'people' ) {
600 foreach $token (@{ $database{ people } }) {
601 if ( $token =~ /^\Q$substr\E/ ) {
602 next if ( ( length($token) ) == $sublen );
603 push( @found, $token );
604 }
605 push( @list, $token );
606 }
607 }
608 $fcnt = scalar(@found);
609 if ( 0 == $len ) {
610 if( $fileprnt eq 'YES' ) {
611 print GREEN "\n@list".RESET."\n";
612 } else {
613 print YELLOW "\n@list".RESET."\n";
614 }
615
616 $term->reset();
617 return CC_REDISPLAY;
618 }
619 elsif ( 1 == $fcnt ) {
620 $term->deletestr($curpos);
621 $str = substr( $str, 0, $curpos );
622 $found[0] .= ' ' if $str !~ /$substr\s/;
623 $str =~ s/\Q$substr\E/$found[0]/;
624 $term->push($str);
625 return CC_REDISPLAY;
626 }
627 elsif (1 < $fcnt ) {
628 @char = split(//, $found[0]);
629 $lth = scalar(@char);
630 $n_cmd = $substr;
631 $ok = 0;
632 for ($ii = $sublen ;$lth -1 > $ii; $ii++) {
633 foreach $token (@found) { $ok++ if ($token =~ /\Q${n_cmd}${char[$ii]}\E/) }
634 if ($fcnt == $ok) {
635 $n_cmd .= $char[$ii];
636 $ok = 0;
637 } else {last}
638 }
639 $term->deletestr($curpos);
640 $str = substr($str, 0, $curpos);
641 $str =~ s/$substr/$n_cmd/;
642 $term->push($str) if $n_cmd;
643 if( $fileprnt eq 'YES' ) {
644 print GREEN "\n@found".RESET."\n";
645 } else {
646 print YELLOW "\n@found".RESET."\n";
647 }
648
649 return CC_REDISPLAY;
650 }
651 return CC_NORM;
652}
653
654# Set up directories
655sub createdir() {
656 &cfg;
657 if( ! -e $files{rootdir} ) {
658 printf("%s", '-'x52);
659 print "\nIt seems you have$r not$rt configured irssi for logging\n";
660 print "Please check out the$y help/info$rt menu\n\n";
661 print "I will go ahead and create the required directories\n";
662 printf("%s$g\n", '-'x52);
663 print "$files{rootdir},\n$files{db}{dir},\n$files{notedir}$rt\n\n";
664 }
665 if( ! -e $files{rootdir} ) { mkdir( $files{rootdir} )};
666 if( ! -e $files{db}{dir} ) { mkdir( $files{db}{dir} )};
667 if( ! -e $files{notedir} ) { mkdir $files{notedir} };
668}
669
670# Information about this program
671sub info() {
672 my $e = "$rt$g";
673 my $b = "$bd";
674print BLUE;
675print <<'EOF';
676 ________ ______ __ _____________
677 / _/ __ \/ ____/ / |/ / ____/ __ \
678 / // /_/ / / / /|_/ / / __/ /_/ /
679 _/ // _, _/ /___ / / / / /_/ / _, _/
680 /___/_/ |_|\____/ /_/ /_/\____/_/ |_|
681EOF
682RESET
683my @string = "$g".
684 "This script was created for personal use, but you are
685 free to use/change it. There may be many bugs or code
686 that is written incorrect. I do not consider myself as
687 a professional \'programmer\' but eventually more things
688 will make sense. This script was designed to keep track
689 of the people and data passing through an IRC channel.
690
691 This program has a few requirements in order to use it
692 to it's full potential. I have configured it to use IRSSI
693 logs and a database file which is created from the logs.
694
695 Here is the configuration required. Start$b irssi$e and set
696 these settings:
697
698 $y/ignore * joins parts quits
699 /set autolog on
700 /set autolog_path ~/irclogs/%Y/\$tag/\$0.%m-%d.log
701 /save$e
702
703 Connect to a channel and let irssi create a log file.
704 Irssi will create a directory structure like this:$b
705 ~/irclogs/<year>/<server>/#<channel>.<month>.<day>.log.$e
706 Let's say you connected to$b irc.freenode.com$e and your
707 channel is $b#irc$e,the log will look like this:$b
708 ~/irclogs/2016/freenode/#irc.09-27.log$rt"
709 .RESET;
710
711print <<EOF;
712 @string
713EOF
714}