ViewVC Help
View File | Revision Log | Show Annotations | Download File
/cvs/cvsroot/Net-IRC3/samples/jsonsrv
Revision: 1.9
Committed: Wed Jan 17 09:29:41 2007 UTC (19 years, 8 months ago) by elmex
Branch: MAIN
Changes since 1.8: +12 -4 lines
Log Message:
changed completion to timeout the displayed partial-completion helpers.
added 'temporary' lines, which can be cleared from the buffers.
improved completion of many strings.
changed uri scheme again to jsirc, as jsirc is a special kind of IRC.
the client handles generally the 'json chat protocol' which works with any
kind of uris.

File Contents

# User Rev Content
1 elmex 1.7 #!/opt/perl/bin/perl
2 elmex 1.1 use strict;
3 elmex 1.5 use URI;
4 elmex 1.1 use AnyEvent::Impl::Perl;
5 elmex 1.7 use IO::Handle;
6 elmex 1.1 use JSON::Syck;
7     use JSONConnection;
8     use Net::IRC3::Client::Connection;
9 elmex 1.7 use POSIX qw/strftime/;
10 elmex 1.1 $Net::IRC3::Client::Connection::DEBUG = 1;
11    
12     our $CFG;
13 elmex 1.5 our %ALIASES;
14 elmex 1.1 our %CONNS;
15    
16 elmex 1.7 our %LOGS;
17    
18 elmex 1.5 our $JS;
19    
20 elmex 1.7 sub log_line {
21     my ($server, $src, $line) = @_;
22     my $logdir = $CFG->{log_dir} || "$ENV{HOME}/.jsonirc_logs/";
23     my $ts = POSIX::strftime "%F %T %Z", localtime (time);
24    
25     eval {
26     unless (-e $logdir) {
27     mkdir $logdir or die "Couldn't make directory '$logdir': $!";
28     }
29     my $logfile = "$logdir/${server}" . ($src ne "" ? "_$src" : "");
30     unless ($LOGS{$logfile}) {
31     open my $logfh, ">>", "$logfile"
32     or die "Couldn't open '$logfile': $!";
33     $LOGS{$logfile} = $logfh;
34     $logfh->autoflush (1);
35     $LOGS{$logfile}->print ("---- $ts ---- starting log ----\n");
36     }
37     $LOGS{$logfile}->print ("$ts: $line\n");
38     };
39     if ($@) {
40     error_reply (undef, undef, logging_error => "Couldn't log for $server $src: $@");
41     }
42     }
43    
44 elmex 1.1 sub load_cfg {
45     $CFG ||= {};
46     return unless -e "$ENV{HOME}/.jsonircrc";
47     open CFGH, "<", "$ENV{HOME}/.jsonircrc" or die "Couldn't open ~/.jsonircrc: $!";
48     $::CFG = JSON::Syck::Load (do { local $/; <CFGH> });
49     }
50    
51     sub save_cfg {
52     open CFGH, ">", "$ENV{HOME}/.jsonircrc" or die "Couldn't open for writing ~/.jsonircrc: $!";
53     print CFGH (JSON::Syck::Dump ($::CFG));
54     close CFGH;
55     }
56    
57 elmex 1.3 sub to_dest_id {
58 elmex 1.5 my ($irccon, $targ) = @_;
59 elmex 1.7 $targ ||= '';
60 elmex 1.5 $irccon = $ALIASES{$irccon} || $irccon;
61     my $uri = new URI;
62 elmex 1.9 $uri->scheme ("jsirc"); $uri->authority ($irccon); $uri->path ($targ);
63 elmex 1.5 "$uri"
64 elmex 1.3 }
65 elmex 1.5
66     sub unalias {
67     my ($id) = @_;
68     for (keys %ALIASES) {
69     if ($id eq $ALIASES{$_}) {
70     return $_;
71     }
72 elmex 1.3 }
73     }
74    
75 elmex 1.5 sub lookup_connection {
76     my ($id) = @_;
77     my $alias = unalias ($id);
78     return $CONNS{$alias || $id}
79     }
80 elmex 1.3
81 elmex 1.5 sub from_dest_id {
82     my ($dest_id) = @_;
83     my $uri = URI->new ($dest_id);
84     my $path = ($uri->path_segments ())[1];
85     return (lookup_connection ($uri->authority), unalias ($uri->authority) || $uri->authority, $path);
86     }
87 elmex 1.1
88 elmex 1.5 sub connect_irc {
89     my ($host, $port, $alias) = @_;
90     my $irccon = "$host:$port";
91     my $pc = $CONNS{$irccon} = Net::IRC3::Client::Connection->new;
92     $ALIASES{$irccon} = $alias if defined $alias;
93 elmex 1.1
94     $pc->reg_cb (
95 elmex 1.6 channel_add => sub {
96     my ($pc, $chan, @nicks) = @_;
97 elmex 1.7 $chan = lc $chan;
98 elmex 1.6 my $dest = to_dest_id ($irccon, $chan);
99     my @ids = map { to_dest_id ($irccon, $_) } @nicks;
100     $JS->broadcast ({
101     src => $dest,
102 elmex 1.7 type => 'subid',
103     command => 'add',
104 elmex 1.6 ids => \@ids,
105     timestamp => time (),
106     });
107 elmex 1.7 log_line ($irccon, $chan, "$chan add: @nicks");
108 elmex 1.6 1;
109     },
110     channel_remove => sub {
111     my ($pc, $chan, @nicks) = @_;
112 elmex 1.7 $chan = lc $chan;
113 elmex 1.6 my $dest = to_dest_id ($irccon, $chan);
114     my @ids = map { to_dest_id ($irccon, $_) } @nicks;
115     $JS->broadcast ({
116     src => $dest,
117 elmex 1.7 type => 'subid',
118     command => 'remove',
119 elmex 1.6 ids => \@ids,
120     timestamp => time (),
121     });
122 elmex 1.7 log_line ($irccon, $chan, "$chan remove: @nicks");
123     1;
124     },
125     channel_change => sub {
126     my ($pc, $chan, $old_nick, $new_nick) = @_;
127     $chan = lc $chan;
128     my $dest = to_dest_id ($irccon, $chan);
129     $JS->broadcast ({
130     src => $dest,
131     type => 'subid',
132     command => 'change',
133     old_id => to_dest_id ($irccon, $old_nick),
134     new_id => to_dest_id ($irccon, $new_nick),
135     timestamp => time (),
136     });
137     log_line ($irccon, $chan, "$chan nick change: $old_nick => $new_nick");
138 elmex 1.6 1;
139     },
140 elmex 1.1 publicmsg => sub {
141     my ($pc, $chan, $msg) = @_;
142 elmex 1.7 $chan = lc $chan;
143 elmex 1.5 my ($nick) = Net::IRC3::Util::split_prefix ($msg->{prefix});
144     my $dest = to_dest_id ($irccon, $chan);
145     my $nickdest = to_dest_id ($irccon, $nick);
146    
147     $JS->broadcast ({
148 elmex 1.3 src => $dest,
149 elmex 1.8 type => (uc ($msg->{command}) eq 'NOTICE' ? "notice" : "message"),
150     msg_scope => "public",
151 elmex 1.1 message => $msg->{trailing},
152 elmex 1.2 timestamp => time (),
153 elmex 1.5 to => { nick => $pc->nick, id => to_dest_id ($irccon, $pc->nick) },
154 elmex 1.4 from => { nick => $nick, id => $nickdest },
155 elmex 1.1 });
156 elmex 1.7 log_line ($irccon, $chan, (uc ($msg->{command}) eq 'NOTICE' ? "{$nick}" : "<$nick>") . " $msg->{trailing}");
157 elmex 1.1 1;
158     },
159     privatemsg => sub {
160     my ($pc, $dsgnick, $msg) = @_;
161     my ($nick) = Net::IRC3::Util::split_prefix ($msg->{prefix});
162 elmex 1.5 my $dest = to_dest_id ($irccon, $nick);
163    
164     $JS->broadcast ({
165 elmex 1.3 src => $dest,
166     type => "message",
167 elmex 1.8 type => (uc ($msg->{command}) eq 'NOTICE' ? "notice" : "message"),
168     msg_scope => "private",
169 elmex 1.1 message => $msg->{trailing},
170 elmex 1.2 timestamp => time (),
171 elmex 1.5 to => { nick => $pc->nick, id => to_dest_id ($irccon, $pc->nick) },
172 elmex 1.4 from => { nick => $nick, id => $dest },
173 elmex 1.1 });
174 elmex 1.7 log_line ($irccon, $nick, (uc ($msg->{command}) eq 'NOTICE' ? "{$nick}" : "<$nick>") . " $msg->{trailing}");
175    
176     1;
177     },
178     error => sub {
179     my ($pc, $code, $message, @params) = @_;
180     my $name = Net::IRC3::Util::rfc_code_to_name ($code);
181     error_reply (undef, undef, irc_error => "$name: $message", to_dest_id ($irccon));
182     log_line ($irccon, "", "$name: $message");
183 elmex 1.1 1;
184     },
185 elmex 1.5 connect => sub {
186 elmex 1.7 info_reply (undef, connected =>
187 elmex 1.5 "Connected to $host:$port "
188 elmex 1.7 . (defined $alias ? "(aka $alias)" : ""),
189     to_dest_id ($irccon)
190 elmex 1.5 );
191 elmex 1.7 log_line ($irccon, "", "connected");
192     1;
193 elmex 1.5 },
194     disconnect => sub {
195 elmex 1.7 error_reply (undef, undef, disconnected =>
196     "Lost connection to $host:$port "
197     . (defined $alias ? "(aka $alias)" : ""),
198     to_dest_id ($irccon)
199 elmex 1.5 );
200 elmex 1.7 log_line ($irccon, "", "disconnected");
201 elmex 1.5 delete $CONNS{"$host:$port"};
202     delete $ALIASES{"$host:$port"};
203 elmex 1.7 1;
204 elmex 1.5 }
205 elmex 1.1 );
206    
207 elmex 1.5 eval {
208     $pc->connect ($host, $port);
209     };
210     if ($@) {
211 elmex 1.7 error_reply (undef, undef, connection_error =>
212 elmex 1.5 "Couldn't connect to $host:$port "
213     . (defined $alias ? "(aka $alias)" : "")
214 elmex 1.7 . ": $@",
215     to_dest_id ($irccon)
216 elmex 1.5 );
217 elmex 1.7 log_line ($irccon, "", "couldn't connect to $host:$port: $@");
218 elmex 1.5 delete $CONNS{"$host:$port"};
219     delete $ALIASES{"$host:$port"};
220     return;
221     }
222 elmex 1.7 my ($nick, $user, $real) = @{
223     $CFG->{userinfo}->{$alias || "$host:$port"}
224     || $CFG->{default_userinfo}
225     || []
226     };
227     $nick or die "No nickname in configuration given";
228     $user ||= $nick;
229     $real ||= $nick;
230     $pc->register ($nick, $user, $real);
231 elmex 1.1
232 elmex 1.5 for (@{$CFG->{channels}->{$alias || "$host:$port"}}) {
233     $pc->send_srv (JOIN => undef => $_);
234 elmex 1.1 }
235     }
236    
237 elmex 1.5 sub update_connections {
238     for (map { /^(\S+):(\d+)/ ? [$1, $2, $_] : [] } keys %CONNS) {
239     my ($h, $p) = @$_;
240     unless (
241     grep {
242     ($_->{host} eq $h) && (($_->{port} || 6667) == $p) && ($_->{connect})
243     } @{$CFG->{servers}})
244     {
245     $CONNS{$_->[2]}->disconnect;
246     }
247     }
248    
249     for my $con (@{$CFG->{servers}}) {
250     my ($host, $port) = ($con->{host}, $con->{port} || 6667);
251    
252     if ($con->{connect} and not $CONNS{"$host:$port"}) {
253 elmex 1.7 eval {
254     connect_irc ($host, $port, $con->{alias});
255     };
256     if ($@) {
257     error_reply (undef, undef,
258     connect_irc => "Couldn't connect to IRC server '$host:$port': $@",
259     to_dest_id ("$host:$port"));
260     log_line ("$host:$port", "", "couldn't connect to $host:$port: $@");
261     }
262 elmex 1.5 }
263     }
264     }
265    
266     sub info_reply {
267 elmex 1.7 my ($lid, $type, $string, $infoid) = @_;
268    
269     my @source = defined $infoid ? (info_id => $infoid) : ();
270    
271 elmex 1.5 if (defined $lid) {
272 elmex 1.7 $JS->send_data ($lid, { type => 'info', info_type => $type, message => $string, @source });
273 elmex 1.5 } else {
274 elmex 1.7 $JS->broadcast ({ type => 'info', info_type => $type, message => $string, @source });
275 elmex 1.5 }
276     }
277    
278     sub error_reply {
279 elmex 1.7 my ($lid, $srcpkt, $type, $string, $errid) = @_;
280    
281     my @source = defined $errid ? (error_id => $errid) : ();
282    
283     my @id = (defined $srcpkt and defined $srcpkt->{id}) ? (id => $srcpkt->{id}) : ();
284    
285 elmex 1.5 if (defined $lid) {
286 elmex 1.7 $JS->send_data ($lid, {
287     timestamp => time,
288     type => 'error',
289     error_type => $type,
290     message => $string,
291     @source,
292     @id
293     });
294 elmex 1.5 } else {
295 elmex 1.7 $JS->broadcast ({
296     timestamp => time,
297     type => 'error',
298     error_type => $type,
299     message => $string,
300     @source,
301     @id
302     });
303     }
304     }
305    
306     sub reply {
307     my ($lid, $srcpkt, $type, @reply) = @_;
308    
309     my @id = (defined $srcpkt and defined $srcpkt->{id}) ? (id => $srcpkt->{id}) : ();
310    
311     if (defined $lid) {
312     $JS->send_data ($lid, {
313     timestamp => time,
314     type => 'reply',
315     reply_type => $type,
316     @reply,
317     @id
318     });
319     } else {
320     $JS->broadcast ({
321     timestamp => time,
322     type => 'reply',
323     reply_type => $type,
324     @reply,
325     @id
326     });
327 elmex 1.5 }
328     }
329    
330     load_cfg;
331    
332     my $c = AnyEvent->condvar;
333    
334     $JS =
335     JSONConnection->new (
336     packet_cb => sub {
337     my ($JS, $lid, $data) = @_;
338    
339     if ($data->{type} eq 'message') {
340     my ($con, $irccon, $dest) = from_dest_id ($data->{dest});
341     unless (defined $con) {
342 elmex 1.7 error_reply ($lid, $data, no_connection => "No connection to ID.", $data->{dest});
343 elmex 1.5 return
344     }
345     $con->send_srv (PRIVMSG => $data->{message} => $dest);
346    
347 elmex 1.7 $JS->broadcast ({
348     src => $data->{dest},
349     type => "message",
350 elmex 1.8 msg_scope => $data->{msg_scope},
351 elmex 1.7 message => $data->{message},
352     is_echo => 1,
353     timestamp => time,
354     to => { nick => $dest, id => $data->{dest} },
355     from => { nick => $con->nick, id => to_dest_id ($irccon, $con->nick) },
356     });
357    
358     log_line ($irccon, $dest, "<".$con->nick."> $data->{message}");
359     reply ($lid, $data, 'sent_message');
360    
361 elmex 1.5 } elsif ($data->{type} eq 'command') {
362     if ($data->{command} eq 'reload') {
363 elmex 1.9 my $old_cfg = $::CFG;
364     eval {
365     load_cfg;
366     update_connections;
367     };
368     if ($@) {
369     $::CFG = $old_cfg;
370     error_reply ($lid, $data, reload_error => "error on reloading: $@");
371     } else {
372     reply ($lid, $data, 'reloaded');
373     }
374 elmex 1.7
375 elmex 1.5 } elsif ($data->{command} eq 'list_connections') {
376     } elsif ($data->{command} eq 'list_ids') {
377     }
378 elmex 1.7 } elsif ($data->{type} eq 'raw') {
379     my $raw = $data->{message};
380     my ($con, $irccon, $dest) = from_dest_id ($data->{dest});
381     unless (defined $con) {
382     error_reply ($lid, $data, no_connection => "No connection to ID.", $data->{dest});
383     return
384     }
385     $con->send_raw ($raw);
386     reply ($lid, $data, 'sent_raw_message');
387    
388 elmex 1.5 } else {
389 elmex 1.7 error_reply ($lid, $data, bad_packet => "Did not understand this packet");
390 elmex 1.5 }
391     1
392     },
393     connect_cb => sub {
394     my ($JS, $lid) = @_;
395     $JS->send_data ($lid, { type => "hello" });
396 elmex 1.7 for my $irccon (keys %CONNS) {
397     for my $chan (keys %{$CONNS{$irccon}->channel_list}) {
398     my $dest = to_dest_id ($irccon, lc $chan);
399     $JS->send_data ($lid, {
400     src => $dest,
401     type => 'subid',
402     command => 'list',
403     ids => [
404     map {
405     to_dest_id ($irccon, $_)
406     } keys %{$CONNS{$irccon}->channel_list ()->{$chan}}
407     ],
408     timestamp => time (),
409     });
410     }
411     }
412 elmex 1.5 });
413    
414     update_connections;
415    
416     $JS->start_listener;
417 elmex 1.1
418     $c->wait;