#!/usr/bin/perl use strict; use utf8; use URI; use JSONConnection; use JSON::Syck; use POSIX qw(strftime); use CursesChatMainwindow; our @highlight; our $CFG; our %known_ids; our %rev_known_ids; our $js; our %completion = map { $_ => 1 } qw{ /buffer /colors /list_buffers /reload /remote_reload /reconnect }; my $d = 0; my $wt; sub timer { $wt = AnyEvent->timer (after => 3, cb => sub { printline (status => [0, "TEST $d"]); $d++; timer (); }); } sub search_buffer { my ($id) = @_; for (keys %{$::CFG->{buffers}}) { if ($::CFG->{buffers}->{$_} eq $id) { return $_; } } return undef } sub write_chatline { my ($buffer, $nick, $time, $msg, $mode) = @_; my $nick_color = $mode eq 'local' ? 7 : ($mode eq 'private' ? 4 : 0); my $ts = '[' . POSIX::strftime ("%T", localtime ($time)) . ']'; my (@chatline) = (("p".length $ts), 0, $ts, 0, " <", $nick_color, "$nick", 0, "> ", 0, $msg); printline ($buffer, \@chatline); } sub write_infoline { my ($buffer, $time, $msg) = @_; my $ts = '[' . POSIX::strftime ("%T", localtime ($time)) . ']'; my (@chatline) = (("p".length $ts), 0, $ts, 0, " ", 3, $msg); printline ($buffer, \@chatline); } sub load_cfg { $CFG ||= {}; return unless -e "$ENV{HOME}/.jsonircclrc"; open CFGH, "<", "$ENV{HOME}/.jsonircclrc" or die "Couldn't open ~/.jsonircclrc: $!"; $::CFG = JSON::Syck::Load (do { local $/; }); } sub maybe_pop_highlight { my ($buffer) = @_; @highlight = grep { $buffer ne $_->[0] } @highlight; printline (msgline => [map { (103, $_->[1] || $_->[0], 0, ' ') } @highlight]); } sub push_highlight { my ($id) = @_; unless (grep { $_->[0] eq $id } (@highlight, [current_buffer ()])) { push @highlight, [$id, search_buffer ($id)]; printline (msgline => [map { (103, $_->[1] || $_->[0], 0, ' ') } @highlight]); } } sub change_buffer { my ($new_buf) = @_; maybe_pop_highlight ($new_buf); select_buffer ($new_buf); } sub prettyfy_id { my ($id) = @_; my $uri = URI->new ($id); my $a = $uri->authority; my $p = ($uri->path_segments ())[1]; $rev_known_ids{"$a/$p"} = $id; "$a/$p" } load_cfg (); my $c = AnyEvent->condvar; #timer (); $js = JSONClientConnection->new ( packet_cb => sub { my ($js, $data) = @_; require JSON::Syck; if ($data->{type} eq 'message') { my ($src_id) = ($data->{src}); my $pretty_id = prettyfy_id ($src_id); $known_ids{$src_id}->{send_id} = $data->{to}->{id}; if ($data->{msg_type} eq 'private') { push_highlight ($pretty_id); } else { for (@{$::CFG->{highlight}}) { if ($data->{message} =~ m/$_/) { push_highlight ($pretty_id); } } } write_chatline ($pretty_id, prettyfy_id ($data->{from}->{id}), $data->{timestamp}, $data->{message}, $data->{msg_type}); } elsif ($data->{type} eq 'metainfo') { my $src_id = $data->{src}; my $pretty_id = prettyfy_id ($src_id); my $it = $data->{info_type}; if ($it eq 'add_ids') { write_infoline ($pretty_id, $data->{timestamp}, "channel add: " . join (', ', map { prettyfy_id ($_) } @{$data->{ids}})); } elsif ($it eq 'remove_ids') { write_infoline ($pretty_id, $data->{timestamp}, "channel remove: " . join (', ', map { prettyfy_id ($_) } @{$data->{ids}})); } elsif ($it eq 'special') { write_infoline ($pretty_id, $data->{timestamp}, "[$data->{message}]"); } } elsif ($data->{type} eq 'info') { printline (status => [3, 'INFO: ' . $data->{message}]); } elsif ($data->{type} eq 'error') { printline (status => [4, 'INFO: ' . $data->{message}]); } printline (debug => [0, JSON::Syck::Dump ($data)]); }, disconnect_cb => sub { printline (status => [4, "ERROR: " . $_[0]]); } ); $js->connect (localhost => 1236); CursesChatMainwindow::init; printline (status => ['p0', 0, "Connected!"]); printline (statusline => [20, "Welcome to the json enhanced chat framework!"]); sub find_common_prefix { my (@words) = @_; @words = sort { length ($a) <=> length ($b) } @words; my $shortest = $words[0]; while ($shortest ne '') { my $no_match = 0; for (@words) { unless (/^\Q$shortest\E/) { $no_match = 1; last; } } return $shortest if not $no_match; substr $shortest, -1, 1, ''; } return ''; } CursesChatMainwindow::register_complete_cb (sub { my ($word, $first_compl, $idx, @line) = @_; # TODO: make completion cycling with $first_compl printline (undef, [0, "f[$first_compl] [$idx] [$word]"]); my @found; if ($idx == 0) { for (keys %completion) { if (/^\Q$word\E/) { push @found, $_; } } } else { my %buffers = map { $_ => 1 } list_buffers; for (keys %rev_known_ids) { delete $buffers{$_}; if (/^\Q$word\E/) { push @found, $_; } } for (keys %buffers) { if (/^\Q$word\E/) { push @found, $_; } } } return "$found[0] " if @found == 1; if (@found) { printline (undef, [0, "$word: " . join ", ", @found]); return find_common_prefix (@found); } return $word; }); CursesChatMainwindow::register_input_cb (sub { my ($input, $escape) = @_; unless (defined $input) { if (exists $CFG->{buffers}->{$escape}) { change_buffer (my $buf = $CFG->{buffers}->{$escape} || 'status'); printline (statusline => [20, "[$buf]"]); } else { change_buffer ('status'); printline (statusline => [20, "[status]"]); } return; } if ($input =~ m/^\/buffer\s*(\S+)/) { change_buffer ("$1"); printline (statusline => [20, "[$1]"]); } elsif ($input =~ m/^\/list_buffers/) { printline (undef, [0, "buffers:"]); for (list_buffers) { printline (undef, [0, "- $_"]); } } elsif ($input =~ m/^\/colors/) { CursesChatMainwindow::print_colors; } elsif ($input =~ m/^\/reconnect\s*(\S*)\s*(\S*)/) { $js->connect ($1 || 'localhost', $2 || 1236); printline (status => [3, "reconnected"]); } elsif ($input =~ m/^\/reload/) { load_cfg (); } elsif ($input =~ m/^\/remote_reload/) { $js->send_data ({ type => 'command', command => 'reload' }); } else { my $pret_id = "" . current_buffer (); my $dest_id = $rev_known_ids{$pret_id}; my $msg = { type => 'message', dest => $dest_id, message => $input }; $js->send_data ($msg); printline (debug => [0, JSON::Syck::Dump ($msg)]); my $mynick = $known_ids{$dest_id}->{send_id} ? prettyfy_id ($known_ids{$dest_id}->{send_id}) : ""; write_chatline ($pret_id, $mynick, time, $input, 'local'); } }); $c->wait; CursesChatMainwindow::end;