PageRenderTime 68ms CodeModel.GetById 27ms RepoModel.GetById 0ms app.codeStats 0ms

/irssi/scripts/query.pl

http://nilicule.googlecode.com/
Perl | 675 lines | 432 code | 182 blank | 61 comment | 85 complexity | c23fbae13fb6255791e0c950a58925fa MD5 | raw file
  1. # query - irssi 0.8.4.CVS
  2. #
  3. # $Id: query.pl,v 1.24 2009/03/29 12:23:10 peder Exp $
  4. #
  5. # Copyright (C) 2001, 2002, 2004, 2007 by Peder Stray <peder@ninja.no>
  6. #
  7. use strict;
  8. use Irssi 20020428.1608;
  9. use Text::Abbrev;
  10. use POSIX;
  11. #use Data::Dumper;
  12. # ======[ Script Header ]===============================================
  13. use vars qw{$VERSION %IRSSI};
  14. ($VERSION) = '$Revision: 1.24 $' =~ / (\d+\.\d+) /;
  15. %IRSSI = (
  16. name => 'query',
  17. authors => 'Peder Stray',
  18. contact => 'peder@ninja.no',
  19. url => 'http://ninja.no/irssi/query.pl',
  20. license => 'GPL',
  21. description => 'Give you more control over when to jump to query windows and when to just tell you one has been created. Enhanced autoclose.',
  22. );
  23. # ======[ Variables ]===================================================
  24. use vars qw(%state);
  25. *state = \%Query::state; # used for tracking idletime and state
  26. my($own);
  27. my(%defaults); # used for storing defaults
  28. my($query_opts) = {}; # stores option abbrevs
  29. # ======[ Helper functions ]============================================
  30. # --------[ load_defaults ]---------------------------------------------
  31. sub load_defaults {
  32. my $file = Irssi::get_irssi_dir."/query";
  33. local *FILE;
  34. %defaults = ();
  35. open FILE, "< $file";
  36. while (<FILE>) {
  37. my($mask,$maxage,$immortal) = split;
  38. $defaults{$mask}{maxage} = $maxage;
  39. $defaults{$mask}{immortal} = $immortal;
  40. }
  41. close FILE;
  42. }
  43. # --------[ save_defaults ]---------------------------------------------
  44. sub save_defaults {
  45. my $file = Irssi::get_irssi_dir."/query";
  46. local *FILE;
  47. open FILE, "> $file";
  48. for (keys %defaults) {
  49. my $d = $defaults{$_};
  50. print FILE join("\t", $_,
  51. exists $d->{maxage} ? $d->{maxage} : -1,
  52. exists $d->{immortal} ? $d->{immortal} : -1,
  53. ), "\n";
  54. }
  55. close FILE;
  56. }
  57. # --------[ sec2str ]---------------------------------------------------
  58. sub sec2str {
  59. my($sec) = @_;
  60. my($ret);
  61. use integer;
  62. $ret = ($sec%60)."s ";
  63. $sec /= 60;
  64. $ret = ($sec%60)."m ".$ret;
  65. $sec /= 60;
  66. $ret = ($sec%24)."h ".$ret;
  67. $sec /= 24;
  68. $ret = $sec."d ".$ret;
  69. $ret =~ s/\b0[dhms] //g;
  70. $ret =~ s/ $//;
  71. return $ret;
  72. }
  73. # --------[ str2sec ]---------------------------------------------------
  74. sub str2sec {
  75. my($str) = @_;
  76. for ($str) {
  77. s/\s+//g;
  78. s/d/*24h/g;
  79. s/h/*60m/g;
  80. s/m/*60s/g;
  81. s/s/+/g;
  82. s/\+$//;
  83. }
  84. if ($str =~ /^[0-9*+]+$/) {
  85. $str = eval $str;
  86. }
  87. else {
  88. $str = 0;
  89. }
  90. return $str;
  91. }
  92. # --------[ set_defaults ]----------------------------------------------
  93. sub set_defaults {
  94. my($serv,$nick,$address) = @_;
  95. my $tag = lc $serv->{tag};
  96. return unless $address;
  97. $state{$tag}{$nick}{address} = $address;
  98. for my $mask (sort {userhost_cmp($serv,$a,$b)}keys %defaults) {
  99. if ($serv->mask_match_address($mask, $nick, $address)) {
  100. for my $key (keys %{$defaults{$mask}}) {
  101. $state{$tag}{$nick}{$key} = $defaults{$mask}{$key}
  102. if $defaults{$mask}{$key} >= 0;
  103. }
  104. }
  105. }
  106. }
  107. # --------[ time2str ]--------------------------------------------------
  108. sub time2str {
  109. my($time) = @_;
  110. return strftime("%c", localtime $time);
  111. }
  112. # --------[ userhost_cmp ]----------------------------------------------
  113. sub userhost_cmp {
  114. my($serv, $am, $bm) = @_;
  115. my($an,$aa) = split "!", $am;
  116. my($bn,$ba) = split "!", $bm;
  117. my($t1,$t2);
  118. $t1 = $serv->mask_match_address($bm, $an, $aa);
  119. $t2 = $serv->mask_match_address($am, $bn, $ba);
  120. return $t1 - $t2 if $t1 || $t2;
  121. $an = $bn = '*';
  122. $am = "$an!$aa";
  123. $bm = "$bn!$ba";
  124. $t1 = $serv->mask_match_address($bm, $an, $aa);
  125. $t2 = $serv->mask_match_address($am, $bn, $ba);
  126. return $t1 - $t2 if $t1 || $t2;
  127. for ($am, $bm, $aa, $ba) {
  128. s/(\*!)?[^*]*@/$1*/;
  129. }
  130. $t1 = $serv->mask_match_address($bm, $an, $aa);
  131. $t2 = $serv->mask_match_address($am, $bn, $ba);
  132. return $t1 - $t2 if $t1 || $t2;
  133. return 0;
  134. }
  135. # ======[ Signal Hooks ]================================================
  136. # --------[ sig_message_own_private ]-----------------------------------
  137. sub sig_message_own_private {
  138. my($server,$msg,$nick,$orig_target) = @_;
  139. $own = $nick;
  140. }
  141. # --------[ sig_message_private ]---------------------------------------
  142. sub sig_message_private {
  143. my($server,$msg,$nick,$addr) = @_;
  144. undef $own;
  145. }
  146. # --------[ sig_print_message ]-----------------------------------------
  147. sub sig_print_message {
  148. my($dest, $text, $strip) = @_;
  149. return unless $dest->{level} & MSGLEVEL_MSGS;
  150. my $server = $dest->{server};
  151. return unless $server;
  152. my $witem = $server->window_item_find($dest->{target});
  153. my $tag = lc $server->{tag};
  154. return unless $witem->{type} eq 'QUERY';
  155. $state{$tag}{$witem->{name}}{time} = time;
  156. }
  157. # --------[ sig_query_address_changed ]---------------------------------
  158. sub sig_query_address_changed {
  159. my($query) = @_;
  160. set_defaults($query->{server}, $query->{name}, $query->{address});
  161. }
  162. # --------[ sig_query_created ]-----------------------------------------
  163. sub sig_query_created {
  164. my ($query, $auto) = @_;
  165. my $qwin = $query->window();
  166. my $awin = Irssi::active_win();
  167. my $serv = $query->{server};
  168. my $nick = $query->{name};
  169. my $tag = lc $query->{server_tag};
  170. if ($auto && $qwin->{refnum} != $awin->{refnum}) {
  171. if ($own eq $query->{name}) {
  172. if (Irssi::settings_get_bool('query_autojump_own')) {
  173. $qwin->set_active();
  174. } else {
  175. $awin->printformat(MSGLEVEL_CLIENTCRAP, 'query_created',
  176. $nick, $query->{server_tag},
  177. $qwin->{refnum})
  178. if Irssi::settings_get_bool('query_noisy');
  179. }
  180. } else {
  181. if (Irssi::settings_get_bool('query_autojump')) {
  182. $qwin->set_active();
  183. } else {
  184. $awin->printformat(MSGLEVEL_CLIENTCRAP, 'query_created',
  185. $nick, $query->{server_tag},
  186. $qwin->{refnum})
  187. if Irssi::settings_get_bool('query_noisy');
  188. }
  189. }
  190. }
  191. undef $own;
  192. $state{$tag}{$nick} = { time => time };
  193. $serv->redirect_event('userhost', 1, ":$nick", -1, undef,
  194. {
  195. "event 302" => "redir query userhost",
  196. "" => "event empty",
  197. });
  198. $serv->send_raw("USERHOST :$nick");
  199. }
  200. # --------[ sig_query_destroyed ]---------------------------------------
  201. sub sig_query_destroyed {
  202. my($query) = @_;
  203. delete $state{lc $query->{server_tag}}{$query->{name}};
  204. }
  205. # --------[ sig_query_nick_changed ]------------------------------------
  206. sub sig_query_nick_changed {
  207. my($query,$old_nick) = @_;
  208. my($tag) = lc $query->{server_tag};
  209. $state{$tag}{$query->{name}} = delete $state{$tag}{$old_nick};
  210. }
  211. # --------[ sig_redir_query_userhost ]----------------------------------
  212. sub sig_redir_query_userhost {
  213. my($serv,$data) = @_;
  214. $data =~ s/^\S*\s*://;
  215. for (split " ", $data) {
  216. if (/([^=*]+)\*?=.(.+)/) {
  217. set_defaults($serv, $1, $2);
  218. }
  219. }
  220. }
  221. # --------[ sig_session_restore ]---------------------------------------
  222. sub sig_session_restore {
  223. open STATE, sprintf "< %s/query.state", Irssi::get_irssi_dir;
  224. %state = (); # only needed if bound as command
  225. while (<STATE>) {
  226. chomp;
  227. my($tag,$nick,%data) = split "\t";
  228. for my $key (keys %data) {
  229. $state{lc $tag}{$nick}{$key} ||= $data{$key};
  230. }
  231. }
  232. close STATE;
  233. }
  234. # --------[ sig_session_save ]------------------------------------------
  235. sub sig_session_save {
  236. open STATE, sprintf "> %s/query.state", Irssi::get_irssi_dir;
  237. for my $tag (keys %state) {
  238. for my $nick (keys %{$state{$tag}}) {
  239. print STATE join("\t",$tag,$nick,%{$state{$tag}{$nick}}), "\n";
  240. }
  241. }
  242. close STATE;
  243. }
  244. # ======[ Timers ]======================================================
  245. # --------[ check_queries ]---------------------------------------------
  246. sub check_queries {
  247. my(@queries) = Irssi::queries;
  248. my($defmax) = Irssi::settings_get_time('query_autoclose')/1000;
  249. my($minage) = Irssi::settings_get_time('query_autoclose_grace')/1000;
  250. my($win) = Irssi::active_win;
  251. for my $query (@queries) {
  252. my $tag = lc $query->{server_tag};
  253. my $name = $query->{name};
  254. my $state = $state{$tag}{$name};
  255. my $age = time - $state->{time};
  256. my $maxage = $defmax;
  257. $maxage = $state->{maxage} if defined $state->{maxage};
  258. # skip the ones we have marked as immortal
  259. next if $state->{immortal};
  260. # maxage = 0 means we have disabled autoclose
  261. next unless $maxage;
  262. # not old enough
  263. next if $age < $maxage;
  264. # unseen messages
  265. next if $query->{data_level} > 1;
  266. # active window
  267. next if $query->is_active &&
  268. $query->window->{refnum} == $win->{refnum};
  269. # graceperiod
  270. next if time - $query->{last_unread_msg} < $minage;
  271. # Figure out what window is associated with the query
  272. my $qwin = $query->window();
  273. # kill it off
  274. Irssi::printformat(MSGLEVEL_CLIENTCRAP, 'query_closed',
  275. $query->{name}, $query->{server_tag})
  276. if Irssi::settings_get_bool('query_noisy');
  277. $query->destroy;
  278. # close the window
  279. $qwin->destroy;
  280. }
  281. }
  282. # ======[ Commands ]====================================================
  283. # --------[ cmd_query ]-------------------------------------------------
  284. sub cmd_query {
  285. my($data,$server,$witem) = @_;
  286. my(@data) = split " ", $data;
  287. my(@params,@opts,$query,$tag,$nick);
  288. my($state,$info,$save);
  289. while (@data) {
  290. my $param = shift @data;
  291. if ($param =~ s/^-//) {
  292. my $opt = $query_opts->{lc $param};
  293. if ($opt) {
  294. if ($opt eq 'window') {
  295. push @opts, "-$param";
  296. } elsif ($opt eq 'immortal') {
  297. $state->{immortal} = 1;
  298. } elsif ($opt eq 'info') {
  299. $info = 1;
  300. } elsif ($opt eq 'mortal') {
  301. $state->{immortal} = 0;
  302. } elsif ($opt eq 'timeout') {
  303. $state->{maxage} = str2sec shift @data;
  304. } elsif ($opt eq 'save') {
  305. $save++;
  306. } else {
  307. # unhandled known opt
  308. }
  309. } elsif ($tag = Irssi::server_find_tag($param)) {
  310. $tag = $tag->{tag};
  311. push @opts, "-$tag";
  312. } else {
  313. # bogus opt...
  314. push @opts, "-$param";
  315. }
  316. } else {
  317. # normal parameter
  318. push @params, $param;
  319. }
  320. }
  321. if (@params) {
  322. Irssi::signal_continue("@opts @params",$server,$witem);
  323. # find the query...
  324. my $serv = Irssi::server_find_tag($tag || $server->{tag});
  325. return unless $serv;
  326. $query = $serv->window_item_find($params[0]);
  327. } else {
  328. if ($witem && $witem->{type} eq 'QUERY') {
  329. $query = $witem;
  330. }
  331. }
  332. if ($query) {
  333. $nick = $query->{name};
  334. $tag = lc $query->{server_tag};
  335. my $opts;
  336. for (keys %$state) {
  337. $state{$tag}{$nick}{$_} = $state->{$_};
  338. $opts++;
  339. }
  340. $state = $state{$tag}{$nick};
  341. if ($info) {
  342. Irssi::signal_stop();
  343. my(@items,$key,$val);
  344. my $timeout = Irssi::settings_get_time('query_autoclose')/1000;
  345. $timeout = $state->{maxage} if defined $state->{maxage};
  346. if ($timeout) {
  347. $timeout .= " (".sec2str($timeout).")";
  348. } else {
  349. $timeout .= " (Off)";
  350. }
  351. @items = (
  352. Server => $query->{server_tag},
  353. Nick => $nick,
  354. Address => $state->{address},
  355. Created => time2str($query->{createtime}),
  356. Immortal => $state->{immortal}?'Yes':'No',
  357. Timeout => $timeout,
  358. Idle => sec2str(time - $state->{time}),
  359. );
  360. $query->printformat(MSGLEVEL_CLIENTCRAP, 'query_info_header');
  361. while (($key,$val) = splice @items, 0, 2) {
  362. $query->printformat(MSGLEVEL_CLIENTCRAP, 'query_info',
  363. $key, $val);
  364. }
  365. $query->printformat(MSGLEVEL_CLIENTCRAP, 'query_info_footer');
  366. return;
  367. }
  368. if ($save) {
  369. Irssi::signal_stop;
  370. unless ($state->{address}) {
  371. $query->printformat(MSGLEVEL_CLIENTCRAP,
  372. 'query_crap', 'This query has no address yet');
  373. return;
  374. }
  375. my $mask = Irssi::Irc::get_mask($nick, $state->{address},
  376. Irssi::Irc::MASK_USER |
  377. Irssi::Irc::MASK_DOMAIN
  378. );
  379. for (qw(immortal maxage)) {
  380. if (exists $state->{$_}) {
  381. $defaults{$mask}{$_} = $state->{$_};
  382. } else {
  383. delete $defaults{$mask}{$_};
  384. }
  385. }
  386. save_defaults;
  387. return;
  388. }
  389. if (!@params) {
  390. Irssi::signal_stop;
  391. return if $opts;
  392. if ($state{$tag}{$nick}{immortal}) {
  393. $witem->printformat(MSGLEVEL_CLIENTCRAP,
  394. 'query_crap', 'This query is immortal');
  395. } else {
  396. $witem->command("unquery")
  397. if Irssi::settings_get_bool('query_unqueries');
  398. }
  399. }
  400. }
  401. }
  402. # --------[ cmd_unquery ]-----------------------------------------------
  403. sub cmd_unquery {
  404. my($data,$server,$witem) = @_;
  405. my($param) = split " ", $data;
  406. my($query,$tag,$nick);
  407. if ($param) {
  408. $query = $server->query_find($param) if $server;
  409. } else {
  410. $query = $witem if $witem && $witem->{type} eq 'QUERY';
  411. }
  412. if ($query) {
  413. $nick = $query->{name};
  414. $tag = lc $query->{server_tag};
  415. if ($state{$tag}{$nick}{immortal}) {
  416. if ($param) {
  417. $witem->printformat(MSGLEVEL_CLIENTCRAP,
  418. 'query_crap',
  419. "Query with $nick is immortal");
  420. } else {
  421. $witem->printformat(MSGLEVEL_CLIENTCRAP,
  422. 'query_crap',
  423. 'This query is immortal');
  424. }
  425. Irssi::signal_stop;
  426. }
  427. }
  428. }
  429. # ======[ Setup ]=======================================================
  430. # --------[ Register commands ]-----------------------------------------
  431. Irssi::command_bind('query', 'cmd_query');
  432. Irssi::command_bind('unquery', 'cmd_unquery');
  433. Irssi::command_set_options('query', 'immortal mortal info save +timeout');
  434. abbrev $query_opts, qw(window immortal mortal info save timeout);
  435. #Irssi::command_bind('debug', sub { print Dumper \%state });
  436. #Irssi::command_bind('query_save', 'sig_session_save');
  437. #Irssi::command_bind('query_restore', 'sig_session_restore');
  438. # --------[ Register formats ]------------------------------------------
  439. Irssi::theme_register(
  440. [
  441. 'query_created',
  442. '{line_start}{hilight Query:} started with {nick $0} [$1] in window $2',
  443. 'query_closed',
  444. '{line_start}{hilight Query:} closed with {nick $0} [$1]',
  445. 'query_info_header', '',
  446. 'query_info_footer', '',
  447. 'query_crap',
  448. '{line_start}{hilight Query:} $0',
  449. 'query_warn',
  450. '{line_start}{hilight Query:} {error Warning:} $0',
  451. 'query_info',
  452. '%#$[8]0: $1',
  453. ]);
  454. # --------[ Register settings ]-----------------------------------------
  455. Irssi::settings_add_bool('query', 'query_autojump_own', 1);
  456. Irssi::settings_add_bool('query', 'query_autojump', 0);
  457. Irssi::settings_add_bool('query', 'query_noisy', 1);
  458. Irssi::settings_add_bool('query', 'query_unqueries',
  459. Irssi::version < 20020919.1507 ||
  460. Irssi::version >= 20021006.1620 );
  461. Irssi::settings_add_time('query', 'query_autoclose', 0);
  462. Irssi::settings_add_time('query', 'query_autoclose_grace', '5min');
  463. # --------[ Register signals ]------------------------------------------
  464. Irssi::signal_add_last('message own_private', 'sig_message_own_private');
  465. Irssi::signal_add_last('message private', 'sig_message_private');
  466. Irssi::signal_add_last('query created', 'sig_query_created');
  467. Irssi::signal_add('print text', 'sig_print_message');
  468. Irssi::signal_add('query address changed', 'sig_query_address_changed');
  469. Irssi::signal_add('query destroyed', 'sig_query_destroyed');
  470. Irssi::signal_add('query nick changed', 'sig_query_nick_changed');
  471. Irssi::signal_add('redir query userhost', 'sig_redir_query_userhost');
  472. Irssi::signal_add('session save', 'sig_session_save');
  473. Irssi::signal_add('session restore', 'sig_session_restore');
  474. # --------[ Register timers ]-------------------------------------------
  475. Irssi::timeout_add(5000, 'check_queries', undef);
  476. # ======[ Initialization ]==============================================
  477. load_defaults;
  478. for my $query (Irssi::queries) {
  479. my($tag) = lc $query->{server_tag};
  480. my($nick) = $query->{name};
  481. $state{$tag}{$nick}{time}
  482. ||= $query->{last_unread_msg} || $query->{createtime} || time;
  483. set_defaults($query->{server}, $nick, $query->{address});
  484. }
  485. if (Irssi::settings_get_time("autoclose_query")) {
  486. Irssi::printformat(MSGLEVEL_CLIENTCRAP, 'query_warn',
  487. "autoclose_query is set, please set to 0");
  488. }
  489. # ======[ END ]=========================================================
  490. # Local Variables:
  491. # header-initial-hide: t
  492. # mode: header-minor
  493. # end: