ViewVC Help
View File | Revision Log | Show Annotations | Download File
/cvs/cvsroot/Net-IRC3/samples/jsonsrv
Revision: 1.11
Committed: Sat Feb 17 13:01:38 2007 UTC (19 years, 7 months ago) by elmex
Branch: MAIN
CVS Tags: HEAD
Changes since 1.10: +0 -0 lines
State: FILE REMOVED
Log Message:
removed json examples,
fixed a few minor bugs and added connect/connect_error events
with improved network code.

File Contents

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