]>
jfr.im git - irc/SurrealServices/srsv.git/blob - branches/0.5.0/modules/serviceslibs/memoserv.pm
1 # This file is part of SurrealServices.
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.
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.
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
26 use SrSv
:: Agent
qw(is_agent) ;
29 use SrSv
:: Text
:: Format
qw(columnar) ;
32 use SrSv
:: User
qw(get_user_nick get_user_id get_user_agent :flood) ;
33 use SrSv
:: User
:: Notice
;
34 use SrSv
:: Help
qw( sendhelp ) ;
36 use SrSv
:: Conf2Consts
qw( main ) ;
38 use SrSv
:: NickReg
:: Flags
;
39 use SrSv
:: NickReg
:: User
qw(is_identified get_nick_user_nicks) ;
41 use SrSv
:: MySQL
qw( $dbh :sql_types ) ;
43 use SrSv
:: Util
qw( makeSeqList seqifyList ) ;
49 our $msnick_default = 'MemoServ1' ;
50 our $msnick = $msnick_default ;
51 our $msuser = { NICK
=> $msnick , ID
=> ircd
:: getAgentUuid
( $msnick ) };
53 $send_memo , $send_chan_memo , $get_chan_recipients ,
57 $get_memo , $get_memo_full , $get_memo_count , $get_unread_memo_count ,
61 $delete_memo , $purge_memos , $delete_all_memos ,
64 $add_ignore , $get_ignore_num , $del_ignore_nick , $list_ignore , $chk_ignore ,
65 $wipe_ignore , $purge_ignore ,
70 $msuser = { NICK
=> $msnick , ID
=> ircd
:: getAgentUuid
( $msnick ) };
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" );
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=?
79 !(nickreg.flags & " . NRF_NOMEMO
() . ")" );
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" );
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" );
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=?" );
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" );
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)
108 ORDER BY ms_ignore.time LIMIT 1 OFFSET ?" );
109 $get_ignore_num -> bind_param ( 2 , 0 , SQL_INTEGER
);
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" );
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=?" );
123 ### MEMOSERV COMMANDS ###
126 $msuser = { NICK
=> $msnick , ID
=> ircd
:: getAgentUuid
( $msnick ) };
127 my ( $user , $dstUser , $msg ) = @_ ;
129 my @args = split ( /\s+/ , $msg );
130 my $cmd = shift @args ;
131 $user -> { AGENT
} = $msuser ;
132 return unless ( lc $dstUser ->{ NICK
} eq lc $msnick );
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." );
139 if ( $cmd =~ /^send$/i ) {
141 my @args = split ( /\s+/ , $msg , 3 );
142 ms_send
( $user , $args [ 1 ], $args [ 2 ], 0 );
144 notice
( $user , 'Syntax: SEND <recipient> <message>' );
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 );
153 notice
( $user , 'Syntax: CSEND <recipient> <uop|vop|hop|aop|sop|cf|founder> <message>' );
156 elsif ( $cmd =~ /^read$/i ) {
157 if ( @args == 1 and ( lc ( $args [ 0 ]) eq 'last' or $args [ 0 ] > 0 or ( lc ( $args [ 0 ]) eq 'unread' ))) {
158 ms_read
( $user , $args [ 0 ]);
160 notice
( $user , 'Syntax: READ <num|LAST|UNREAD>' );
163 elsif ( $cmd =~ /^list$/i ) {
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 ]);
170 notice
( $user , 'Syntax: DELETE <num|num1-num2|ALL>' );
173 elsif ( $cmd =~ /^ign(ore)?$/i ) {
174 my $cmd2 = shift @args ;
175 if ( $cmd2 =~ /^a(dd)?$/i ) {
177 ms_ignore_add
( $user , $args [ 0 ]);
180 notice
( $user , 'Syntax: IGNORE ADD <nick>' );
183 elsif ( $cmd2 =~ /^d(el)?$/i ) {
185 ms_ignore_del
( $user , $args [ 0 ]);
188 notice
( $user , 'Syntax: IGNORE DEL [nick|num]' );
191 elsif ( $cmd2 =~ /^l(ist)?$/i ) {
192 ms_ignore_list
( $user );
195 notice
( $user , 'Syntax: IGNORE <ADD|DEL|LIST> [nick|num]' );
198 elsif ( $cmd =~ /^help$/i ) {
199 sendhelp
( $user , 'memoserv' , @args );
202 notice
( $user , "Unrecognized command. For help, type: \002 /ms help \002 " );
207 my ( $user , $dst , $msg , $level ) = @_ ;
208 my $src = get_user_nick
( $user );
210 my $root = auth
( $user ) or return ;
212 if ( length ( $msg ) > MAX_MEMO_LEN
()) {
213 notice
( $user , 'Memo too long. Maximum memo length is ' . MAX_MEMO_LEN
(). ' characters.' );
218 my $chan = { CHAN
=> $dst };
219 unless ( chanserv
:: is_registered
( $chan )) {
220 notice
( $user , " $dst is not registered" );
224 my $srcnick = chanserv
:: can_do
( $chan , 'MEMO' , $user ) or return ;
226 send_chan_memo
( $srcnick , $chan , $msg , $level );
228 nickserv
:: chk_registered
( $user , $dst ) or return ;
230 if ( nr_chk_flag
( $dst , NRF_NOMEMO
(), + 1 )) {
231 notice
( $user , " \002 $dst \002 is not accepting memos." );
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." );
240 send_memo
( $src , $dst , $msg );
243 notice
( $user , "Your memo has been sent." );
247 my ( $user , $num ) = @_ ;
248 my ( $from , $chan , $time , $flag , $msg );
249 my $src = get_user_nick
( $user );
251 my $root = auth
( $user ) or return ;
254 if ( lc ( $num ) eq 'last' ) {
255 $get_memo_count -> execute ( $root );
256 ( $num ) = $get_memo_count -> fetchrow_array ;
258 notice
( $user , "Memo \002 $num \002 not found." );
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." );
270 $set_flag -> execute ( 1 , $from , $root , $chan , $time );
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 );
279 @nums = makeSeqList
( $num );
284 while ( my $num = shift @nums ) {
286 push @reply , "You can only read 5 memos at a time." ;
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." ;
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 , ' --' ;
299 notice
( $user , @reply );
304 my ( $i , @data , $mnlen , $mclen );
305 my $src = get_user_nick
( $user );
307 my $root = auth
( $user ) or return ;
309 $get_memo_list -> execute ( $root );
310 while ( my ( $from , $chan , $time , $flag , $msg ) = $get_memo_list -> fetchrow_array ) {
314 ( $flag ? '' : " \002 " ) . $i ,
315 $from , $chan , gmtime2
( $time ),
316 ( length ( $msg ) > 20 ? substr ( $msg , 0 , 17 ) . '...' : $msg )
321 notice
( $user , "You have no memos." );
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 ));
330 my ( $user , @args ) = @_ ;
331 my $src = get_user_nick
( $user );
333 my $root = auth
( $user ) or return ;
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.' );
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" );
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 );
352 push @notDeleted , $num ;
355 if ( scalar ( @deleted )) {
356 my $plural = ( scalar ( @deleted ) == 1 );
357 my $msg = sprintf ( "Memo %s deleted: " . join ( ', ' , seqifyList
@deleted ), ( $plural ? '' : 's' ));
360 if ( scalar ( @notDeleted )) {
361 my $msg = sprintf ( "Memos not found: " . join ( ', ' , seqifyList
@notDeleted ));
366 sub ms_ignore_add
( $$ ) {
367 my ( $user , $nick ) = @_ ;
368 my $src = get_user_nick
( $user );
370 unless ( is_identified
( $user , $src ) or adminserv
:: can_do
( $user , 'SERVOP' )) {
371 notice
( $user , $err_deny );
375 my $nickroot = nickserv
:: get_root_nick
( $nick );
377 notice
( $user , " $nick is not registered" );
381 my $srcroot = nickserv
:: get_root_nick
( $src );
383 $add_ignore -> execute ( $srcroot , $nickroot );
385 notice
( $user , " \002 $nick \002 ( \002 $nickroot \002 ) added to \002 $src \002 ( \002 $srcroot \002 ) memo ignore list." );
388 sub ms_ignore_del
( $$ ) {
389 my ( $user , $entry ) = @_ ;
390 my $src = get_user_nick
( $user );
392 unless ( is_identified
( $user , $src ) or adminserv
:: can_do
( $user , 'SERVOP' )) {
393 notice
( $user , $err_deny );
396 my $srcroot = nickserv
:: get_root_nick
( $src );
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 ();
404 my $ret = $del_ignore_nick -> execute ( $srcroot , ( $ignorenick ? $ignorenick : $entry ));
406 notice
( $user , "Delete succeeded for ( $srcroot ): $entry " );
409 notice
( $user , "Delete failed for ( $srcroot ): $entry . entry does not exist?" );
413 sub ms_ignore_list
($) {
415 my $src = get_user_nick
( $user );
417 unless ( is_identified
( $user , $src ) or adminserv
:: can_do
( $user , 'SERVOP' )) {
418 notice
( $user , $err_deny );
421 my $srcroot = nickserv
:: get_root_nick
( $src );
424 $list_ignore -> execute ( $srcroot );
425 while ( my ( $nick , $time ) = $list_ignore -> fetchrow_array ) {
426 push @data , [ $nick , '(' . gmtime2
( $time ). ')' ];
429 notice
( $user , columnar
({ TITLE
=> "Memo ignore list for \002 $src \002 :" ,
430 NOHIGHLIGHT
=> nr_chk_flag_user
( $user , NRF_NOHIGHLIGHT
)}, @data ));
434 my ( $user , $root ) = @_ ;
437 unless ( ref ( $user ) eq "HASH" ) {
438 $user = { NICK
=> $user };
441 if ( $root ) { @nicks = ( $root ) }
442 else { @nicks = nickserv
:: get_id_nicks
( $user ) }
445 foreach my $n ( @nicks ) {
446 $get_unread_memo_count -> execute ( $n );
447 my ( $c ) = $get_unread_memo_count -> fetchrow_array ;
449 notice
( $user , "You have \002 $c \002 unread memo(s). " . ( @nicks > 1 ? "( \002 $n \002 ) " : '' ));
453 notice
( $user , "To view them, type: \002 /ms list \002 " ) if $hasmemos ;
456 ### DATABASE UTILITY FUNCTIONS ###
459 my ( $src , $dst , $msg ) = @_ ;
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 );
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 " );
471 sub 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 ));
476 $send_chan_memo -> execute ( $src , $cn , time (), $msg , $cn , $level );
477 # "INSERT INTO memo SELECT ?, nick, ?, ?, 0, ? FROM chanacc WHERE chan=? AND level >= ?"
479 $get_chan_recipients -> execute ( $cn , $level );
480 while ( my ( $u ) = $get_chan_recipients -> fetchrow_array ) {
481 notice
({ NICK
=> $u , AGENT
=> $msuser },
482 "You have a new memo from \002 $src \002 to \002 $cn \002 . To read it, type: \002 /ms read last \002 " );
486 sub notice_all_nicks
( $$ ) {
487 my ( $nick , $msg ) = @_ ;
489 foreach my $u ( get_nick_user_nicks
$nick ) {
490 notice
({ NICK
=> $u , AGENT
=> $msuser }, $msg );
496 my $src = get_user_nick
( $user );
498 my $root = nickserv
:: get_root_nick
( $src );
500 notice
( $user , "Your nick is not registered." );
504 unless ( is_identified
( $user , $root )) {
505 notice
( $user , $err_deny );