]> jfr.im git - irc/SurrealServices/srsv.git/blame - branches/erry-devel/modules/serviceslibs/memoserv.pm
My work on this so far....
[irc/SurrealServices/srsv.git] / branches / erry-devel / modules / serviceslibs / memoserv.pm
CommitLineData
5975999e 1# This file is part of SurrealServices.
2#
3# SurrealServices is free software; you can redistribute it and/or modify
4# it under the terms of the GNU General Public License as published by
5# the Free Software Foundation; either version 2 of the License, or
6# (at your option) any later version.
7#
8# SurrealServices is distributed in the hope that it will be useful,
9# but WITHOUT ANY WARRANTY; without even the implied warranty of
10# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11# GNU General Public License for more details.
12#
13# You should have received a copy of the GNU General Public License
14# along with SurrealServices; if not, write to the Free Software
15# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
16package memoserv;
17
18use strict;
19use DBI qw(:sql_types);
20#use constant {
21# READ => 1,
22# DEL => 2,
23# ACK => 4,
24# NOEXP => 8
25#};
26
27use SrSv::Agent qw(is_agent);
28
29use SrSv::Time;
30use SrSv::Text::Format qw(columnar);
31use SrSv::Errors;
32
33use SrSv::User qw(get_user_nick get_user_id :flood);
34use SrSv::User::Notice;
35use SrSv::Help qw( sendhelp );
36
37use SrSv::NickReg::Flags;
38use SrSv::NickReg::User qw(is_identified get_nick_user_nicks);
39
40use SrSv::MySQL '$dbh';
41
42use SrSv::Util qw( makeSeqList );
43
44use constant (
45 MAX_MEMO_LEN => 400
46);
47
48our $msnick_default = 'MemoServ';
49our $msnick = $msnick_default;
2eef9154 50our $msuser = { NICK => $msnick, ID => "123AAAAAE" };
5975999e 51our (
52 $send_memo, $send_chan_memo, $get_chan_recipients,
53
54 $get_memo_list,
55
56 $get_memo, $get_memo_full, $get_memo_count, $get_unread_memo_count,
57
58 $set_flag,
59
60 $delete_memo, $purge_memos, $delete_all_memos,
61 $memo_chgroot,
62
63 $add_ignore, $get_ignore_num, $del_ignore_nick, $list_ignore, $chk_ignore,
64 $wipe_ignore, $purge_ignore,
2eef9154 65 $get_first_unread
5975999e 66);
67
68sub init() {
2eef9154 69 $get_first_unread = $dbh->prepare("SELECT memo.src, memo.chan, memo.time, memo.flag, memo.msg FROM memo, nickreg WHERE nickreg.nick=? AND memo.dstid=nickreg.id AND memo.flag=0 LIMIT 1");
5975999e 70 $send_memo = $dbh->prepare("INSERT INTO memo SELECT ?, id, NULL, UNIX_TIMESTAMP(), NULL, ? FROM nickreg WHERE nick=?");
71 $send_chan_memo = $dbh->prepare("INSERT INTO memo SELECT ?, nickreg.id, ?, ?, NULL, ? FROM chanacc, nickreg
72 WHERE chanacc.chan=? AND chanacc.level >= ? AND chanacc.nrid=nickreg.id
73 AND !(nickreg.flags & ". NRF_NOMEMO() . ")");
74 $get_chan_recipients = $dbh->prepare("SELECT user.nick FROM user, nickid, nickreg, chanacc WHERE
75 user.id=nickid.id AND nickid.nrid=chanacc.nrid AND chanacc.nrid=nickreg.id AND chanacc.chan=?
76 AND level >= ? AND
77 !(nickreg.flags & ". NRF_NOMEMO() . ")");
78
79 $get_memo_list = $dbh->prepare("SELECT memo.src, memo.chan, memo.time, memo.flag, memo.msg FROM memo, nickreg WHERE nickreg.nick=? AND memo.dstid=nickreg.id ORDER BY memo.time ASC");
80
81 $get_memo = $dbh->prepare("SELECT memo.src, memo.chan, memo.time
82 FROM memo JOIN nickreg ON (memo.dstid=nickreg.id) WHERE nickreg.nick=? ORDER BY memo.time ASC LIMIT 1 OFFSET ?");
83 $get_memo->bind_param(2, 0, SQL_INTEGER);
84 $get_memo_full = $dbh->prepare("SELECT memo.src, memo.chan, memo.time, memo.flag, memo.msg FROM memo, nickreg WHERE nickreg.nick=? AND memo.dstid=nickreg.id ORDER BY memo.time ASC LIMIT 1 OFFSET ?");
85 $get_memo_full->bind_param(2, 0, SQL_INTEGER);
86 $get_memo_count = $dbh->prepare("SELECT COUNT(*) FROM memo, nickreg WHERE nickreg.nick=? AND memo.dstid=nickreg.id");
87 $get_unread_memo_count = $dbh->prepare("SELECT COUNT(*) FROM memo, nickreg WHERE nickreg.nick=? AND memo.dstid=nickreg.id AND memo.flag=0");
88
89 $set_flag = $dbh->prepare("UPDATE memo, nickreg SET memo.flag=? WHERE memo.src=? AND nickreg.nick=? AND memo.dstid=nickreg.id AND memo.chan=? AND memo.time=?");
90
91 $delete_memo = $dbh->prepare("DELETE FROM memo USING memo, nickreg WHERE memo.src=? AND nickreg.nick=? AND memo.dstid=nickreg.id AND memo.chan=? AND memo.time=?");
92 $purge_memos = $dbh->prepare("DELETE FROM memo USING memo, nickreg WHERE nickreg.nick=? AND memo.dstid=nickreg.id AND memo.flag=1");
93 $delete_all_memos = $dbh->prepare("DELETE FROM memo USING memo, nickreg WHERE nickreg.nick=? AND memo.dstid=nickreg.id");
94
95 $add_ignore = $dbh->prepare("INSERT INTO ms_ignore (ms_ignore.nrid, ms_ignore.ignoreid, time)
96 SELECT nickreg.id, ignorenick.id, UNIX_TIMESTAMP() FROM nickreg, nickreg AS ignorenick
97 WHERE nickreg.nick=? AND ignorenick.nick=?");
98 $del_ignore_nick = $dbh->prepare("DELETE FROM ms_ignore USING ms_ignore
99 JOIN nickreg ON (ms_ignore.nrid=nickreg.id)
100 JOIN nickreg AS ignorenick ON(ms_ignore.ignoreid=ignorenick.id)
101 WHERE nickreg.nick=? AND ignorenick.nick=?");
102 $get_ignore_num = $dbh->prepare("SELECT ignorenick.nick FROM ms_ignore
103 JOIN nickreg ON (ms_ignore.nrid=nickreg.id)
104 JOIN nickreg AS ignorenick ON(ms_ignore.ignoreid=ignorenick.id)
105 WHERE nickreg.nick=?
106 ORDER BY ms_ignore.time LIMIT 1 OFFSET ?");
107 $get_ignore_num->bind_param(2, 0, SQL_INTEGER);
108
109 $list_ignore = $dbh->prepare("SELECT ignorenick.nick, ms_ignore.time
110 FROM ms_ignore, nickreg, nickreg AS ignorenick
111 WHERE nickreg.nick=? AND ms_ignore.nrid=nickreg.id AND ms_ignore.ignoreid=ignorenick.id
112 ORDER BY ms_ignore.time");
113 $chk_ignore = $dbh->prepare("SELECT 1
114 FROM ms_ignore, nickreg, nickreg AS ignorenick
115 WHERE nickreg.nick=? AND ms_ignore.nrid=nickreg.id AND ignorenick.nick=? AND ms_ignore.ignoreid=ignorenick.id");
116
117 $wipe_ignore = $dbh->prepare("DELETE FROM ms_ignore USING ms_ignore JOIN nickreg ON(ms_ignore.nrid=nickreg.id) WHERE nickreg.nick=?");
118 $purge_ignore = $dbh->prepare("DELETE FROM ms_ignore USING ms_ignore JOIN nickreg ON(ms_ignore.ignoreid=nickreg.id) WHERE nickreg.nick=?");
119}
120
121### MEMOSERV COMMANDS ###
122
123sub dispatch($$$) {
2eef9154 124 my ($user, $dstUser, $msg) = @_;
5975999e 125 $msg =~ s/^\s+//;
126 my @args = split(/\s+/, $msg);
127 my $cmd = shift @args;
2eef9154 128 $user -> {AGENT} = $dstUser;
129 return unless (lc $dstUser->{NICK} eq lc $msnick);
130 get_user_id ($user);
5975999e 131 return if flood_check($user);
132
133 if($cmd =~ /^send$/i) {
134 if(@args >= 2) {
135 my @args = split(/\s+/, $msg, 3);
136 ms_send($user, $args[1], $args[2], 0);
137 } else {
138 notice($user, 'Syntax: SEND <recipient> <message>');
139 }
140 }
141 elsif($cmd =~ /^csend$/i) {
142 if(@args >= 3 and $args[1] =~ /^(?:[uvhas]op|co?f(ounder)?|founder)$/i) {
143 my @args = split(/\s+/, $msg, 4);
144 my $level = chanserv::xop_byname($args[2]);
145 ms_send($user, $args[1], $args[3], $level);
146 } else {
147 notice($user, 'Syntax: CSEND <recipient> <uop|vop|hop|aop|sop|cf|founder> <message>');
148 }
149 }
150 elsif($cmd =~ /^read$/i) {
2eef9154 151 if(@args == 1 and (lc($args[0]) eq 'last' or $args[0] > 0 or (lc($args[0]) eq 'unread'))) {
5975999e 152 ms_read($user, $args[0]);
153 } else {
2eef9154 154 notice($user, 'Syntax: READ <num|LAST|UNREAD>');
5975999e 155 }
156 }
157 elsif($cmd =~ /^list$/i) {
158 ms_list($user);
159 }
160 elsif($cmd =~ /^del(ete)?$/i) {
161 if(@args >= 1 and (lc($args[0]) eq 'all' or $args[0] > 0)) {
162 ms_delete($user, $args[0]);
163 } else {
164 notice($user, 'Syntax: DELETE <num|num1-num2|ALL>');
165 }
166 }
167 elsif($cmd =~ /^ign(ore)?$/i) {
168 my $cmd2 = shift @args;
169 if($cmd2 =~ /^a(dd)?$/i) {
170 if(@args == 1) {
171 ms_ignore_add($user, $args[0]);
172 }
173 else {
174 notice($user, 'Syntax: IGNORE ADD <nick>');
175 }
176 }
177 elsif($cmd2 =~ /^d(el)?$/i) {
178 if(@args == 1) {
179 ms_ignore_del($user, $args[0]);
180 }
181 else {
182 notice($user, 'Syntax: IGNORE DEL [nick|num]');
183 }
184 }
185 elsif($cmd2 =~ /^l(ist)?$/i) {
186 ms_ignore_list($user);
187 }
188 else {
189 notice($user, 'Syntax: IGNORE <ADD|DEL|LIST> [nick|num]');
190 }
191 }
192 elsif($cmd =~ /^help$/i) {
193 sendhelp($user, 'memoserv', @args);
194 }
195 else {
196 notice($user, "Unrecognized command. For help, type: \002/ms help\002");
197 }
198}
199
200sub ms_send($$$$) {
201 my ($user, $dst, $msg, $level) = @_;
202 my $src = get_user_nick($user);
203
204 my $root = auth($user) or return;
205
206 if(length($msg) > MAX_MEMO_LEN()) {
207 notice($user, 'Memo too long. Maximum memo length is '.MAX_MEMO_LEN().' characters.');
208 return;
209 }
210
211 if($dst =~ /^#/) {
212 my $chan = { CHAN => $dst };
213 unless(chanserv::is_registered($chan)) {
214 notice($user, "$dst is not registered");
215 return;
216 }
217
218 my $srcnick = chanserv::can_do($chan, 'MEMO', $user) or return;
219
220 send_chan_memo($srcnick, $chan, $msg, $level);
221 } else {
222 nickserv::chk_registered($user, $dst) or return;
223
224 if (nr_chk_flag($dst, NRF_NOMEMO(), +1)) {
225 notice($user, "\002$dst\002 is not accepting memos.");
226 return;
227 }
228 $chk_ignore->execute(nickserv::get_root_nick($dst), $root);
229 if ($chk_ignore->fetchrow_array) {
230 notice($user, "\002$dst\002 is not accepting memos.");
231 return;
232 }
233
234 send_memo($src, $dst, $msg);
235 }
236
237 notice($user, "Your memo has been sent.");
238}
239
240sub ms_read($$) {
241 my ($user, $num) = @_;
242 my ($from, $chan, $time, $flag, $msg);
243 my $src = get_user_nick($user);
244
245 my $root = auth($user) or return;
246
247 my @nums;
248 if(lc($num) eq 'last') {
249 $get_memo_count->execute($root);
250 ($num) = $get_memo_count->fetchrow_array;
251 if (!$num) {
252 notice($user, "Memo \002$num\002 not found.");
253 return;
254 }
255 @nums = ($num);
2eef9154 256 }
257 elsif (lc($num) eq 'unread') {
258 $get_first_unread->execute($root);
259 $get_memo_full->execute($root, $num-1);
260 unless(($from, $chan, $time, $flag, $msg) = $get_first_unread->fetchrow_array) {
261 notice($user, "You have no unread memos.");
262 return;
263 }
264 $set_flag->execute(1, $from, $root, $chan, $time);
265 my @reply;
266 push @reply, "Memo \002$num\002 from \002$from\002 ".
267 ($chan ? "to \002$chan\002 " : "to \002$root\002 ").
268 "at ".gmtime2($time), $msg, ' --';
269 notice ($user, @reply);
270 return;
271 }
272 else {
5975999e 273 @nums = makeSeqList($num);
274 }
275
276 my $count = 0;
277 my @reply;
278 while (my $num = shift @nums) {
279 if (++$count > 5) {
280 push @reply, "You can only read 5 memos at a time.";
281 last;
282 }
283 $get_memo_full->execute($root, $num-1);
284 unless(($from, $chan, $time, $flag, $msg) = $get_memo_full->fetchrow_array) {
285 push @reply, "Memo \002$num\002 not found.";
286 next;
287 }
288 $set_flag->execute(1, $from, $root, $chan, $time);
289 push @reply, "Memo \002$num\002 from \002$from\002 ".
290 ($chan ? "to \002$chan\002 " : "to \002$root\002 ").
291 "at ".gmtime2($time), ' ', ' '.$msg, ' --';
292 }
293 notice($user, @reply);
294}
295
296sub ms_list($) {
297 my ($user) = @_;
298 my ($i, @data, $mnlen, $mclen);
299 my $src = get_user_nick($user);
300
301 my $root = auth($user) or return;
302
303 $get_memo_list->execute($root);
304 while(my ($from, $chan, $time, $flag, $msg) = $get_memo_list->fetchrow_array) {
305 $i++;
306
307 push @data, [
308 ($flag ? '' : "\002") . $i,
309 $from, $chan, gmtime2($time),
310 (length($msg) > 20 ? substr($msg, 0, 17) . '...' : $msg)
311 ];
312 }
313
314 unless(@data) {
315 notice($user, "You have no memos.");
316 return;
317 }
318
319 notice($user, columnar( { TITLE => "Memo list for \002$root\002. To read, type \002/ms read <num>\002",
320 NOHIGHLIGHT => nr_chk_flag_user($user, NRF_NOHIGHLIGHT) }, @data));
321}
322
323sub ms_delete($@) {
324 my ($user, @args) = @_;
325 my $src = get_user_nick($user);
326
327 my $root = auth($user) or return;
328
329 if(scalar(@args) == 1 and lc($args[0]) eq 'all') {
330 $delete_all_memos->execute($root);
331 notice($user, 'All of your memos have been deleted.');
332 return;
333 }
334 my (@deleted, @notDeleted);
335 foreach my $num (reverse makeSeqList(@args)) {
336 if(int($num) ne $num) { # can this happen, given makeSeqList?
337 notice($user, "\002$num\002 is not an integer number");
338 next;
339 }
340 my ($from, $chan, $time);
341 $get_memo->execute($root, $num-1);
342 if(my ($from, $chan, $time) = $get_memo->fetchrow_array) {
343 $delete_memo->execute($from, $root, $chan, $time);
344 push @deleted, $num;
345 } else {
346 push @notDeleted, $num;
347 }
348 }
349 if(scalar(@deleted)) {
350 my $plural = (scalar(@deleted) == 1);
351 my $msg = sprintf("Memo%s deleted: ".join(', ', @deleted), ($plural ? '' : 's'));
352 notice($user, $msg);
353 }
354 if(scalar(@notDeleted)) {
355 my $msg = sprintf("Memos not found: ".join(', ', @notDeleted));
356 notice($user, $msg);
357 }
358}
359
360sub ms_ignore_add($$) {
361 my ($user, $nick) = @_;
362 my $src = get_user_nick($user);
363
364 unless(is_identified($user, $src) or adminserv::can_do($user, 'SERVOP')) {
365 notice($user, $err_deny);
366 return;
367 }
368
369 my $nickroot = nickserv::get_root_nick($nick);
370 unless ($nickroot) {
371 notice($user, "$nick is not registered");
372 return;
373 }
374
375 my $srcroot = nickserv::get_root_nick($src);
376
377 $add_ignore->execute($srcroot, $nickroot);
378
379 notice($user, "\002$nick\002 (\002$nickroot\002) added to \002$src\002 (\002$srcroot\002) memo ignore list.");
380}
381
382sub ms_ignore_del($$) {
383 my ($user, $entry) = @_;
384 my $src = get_user_nick($user);
385
386 unless(is_identified($user, $src) or adminserv::can_do($user, 'SERVOP')) {
387 notice($user, $err_deny);
388 return;
389 }
390 my $srcroot = nickserv::get_root_nick($src);
391
392 my $ignorenick;
393 if (misc::isint($entry)) {
394 $get_ignore_num->execute($srcroot, $entry - 1);
395 ($ignorenick) = $get_ignore_num->fetchrow_array();
396 $get_ignore_num->finish();
397 }
398 my $ret = $del_ignore_nick->execute($srcroot, ($ignorenick ? $ignorenick : $entry));
399 if($ret == 1) {
400 notice($user, "Delete succeeded for ($srcroot): $entry");
401 }
402 else {
403 notice($user, "Delete failed for ($srcroot): $entry. entry does not exist?");
404 }
405}
406
407sub ms_ignore_list($) {
408 my ($user) = @_;
409 my $src = get_user_nick($user);
410
411 unless(is_identified($user, $src) or adminserv::can_do($user, 'SERVOP')) {
412 notice($user, $err_deny);
413 return;
414 }
415 my $srcroot = nickserv::get_root_nick($src);
416
417 my @data;
418 $list_ignore->execute($srcroot);
419 while (my ($nick, $time) = $list_ignore->fetchrow_array) {
420 push @data, [$nick, '('.gmtime2($time).')'];
421 }
422
423 notice($user, columnar({TITLE => "Memo ignore list for \002$src\002:",
424 NOHIGHLIGHT => nr_chk_flag_user($user, NRF_NOHIGHLIGHT)}, @data));
425}
426
427sub notify($;$) {
428 my ($user, $root) = @_;
429 my (@nicks);
430
2eef9154 431 unless(ref($user) eq "HASH") {
5975999e 432 $user = { NICK => $user };
433 }
434
435 if($root) { @nicks = ($root) }
436 else { @nicks = nickserv::get_id_nicks($user) }
437
438 my $hasmemos;
439 foreach my $n (@nicks) {
440 $get_unread_memo_count->execute($n);
441 my ($c) = $get_unread_memo_count->fetchrow_array;
442 next unless $c;
443 notice($user, "You have \002$c\002 unread memo(s). " . (@nicks > 1 ? "(\002$n\002) " : ''));
444 $hasmemos = 1;
445 }
446
447 notice($user, "To view them, type: \002/ms list\002") if $hasmemos;
448}
449
450### DATABASE UTILITY FUNCTIONS ###
451
452sub send_memo($$$) {
453 my ($src, $dst, $msg) = @_;
454
455 # This construct is intended to allow agents to send memos.
456 # Unfortunately this is raceable against %nickserv::enforcers.
457 # I don't want to change the %nickserv::enforcers decl tho, s/my/our/
458 $src = (is_agent($src) ? $src : nickserv::get_root_nick($src));
459 $dst = nickserv::get_root_nick($dst);
460
461 $send_memo->execute($src, $msg, $dst);
462 notice_all_nicks($dst, "You have a new memo from \002$src\002. To read it, type: \002/ms read last\002");
463}
464
465sub send_chan_memo($$$$) {
466 my ($src, $chan, $msg, $level) = @_;
467 my $cn = $chan->{CHAN};
468 $src = (is_agent($src) ? $src : nickserv::get_root_nick($src));
469
470 $send_chan_memo->execute($src, $cn, time(), $msg, $cn, $level);
471 # "INSERT INTO memo SELECT ?, nick, ?, ?, 0, ? FROM chanacc WHERE chan=? AND level >= ?"
472
473 $get_chan_recipients->execute($cn, $level);
474 while(my ($u) = $get_chan_recipients->fetchrow_array) {
2eef9154 475 notice({ NICK => $u, AGENT => $msuser },
5975999e 476 "You have a new memo from \002$src\002 to \002$cn\002. To read it, type: \002/ms read last\002");
477 }
478}
479
480sub notice_all_nicks($$) {
481 my ($nick, $msg) = @_;
482
483 foreach my $u (get_nick_user_nicks $nick) {
2eef9154 484 notice({ NICK => $u, AGENT => $msuser }, $msg);
5975999e 485 }
486}
487
488sub auth($) {
489 my ($user) = @_;
490 my $src = get_user_nick($user);
491
492 my $root = nickserv::get_root_nick($src);
493 unless($root) {
494 notice($user, "Your nick is not registered.");
495 return 0;
496 }
497
498 unless(is_identified($user, $root)) {
499 notice($user, $err_deny);
500 return 0;
501 }
502
503 return $root;
504}
505
506### IRC EVENTS ###
507
5081;