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