From db980877308c29903b4867e74f545185c9a3ceb7 Mon Sep 17 00:00:00 2001 From: q3aql Date: Fri, 2 Apr 2021 13:04:11 +0200 Subject: [PATCH] Scripts backup from msparks/irssiscripts --- scripts/anames.pl | 233 ++++++++++++++++ scripts/automode.pl | 321 +++++++++++++++++++++ scripts/bitlbee_autoreply.pl | 62 +++++ scripts/bitlbee_html.pl | 460 +++++++++++++++++++++++++++++++ scripts/bitlbee_status_notice.pl | 211 ++++++++++++++ scripts/bitlbee_typing_notice.pl | 319 +++++++++++++++++++++ scripts/grumble.pl | 115 ++++++++ scripts/hilight_notice.pl | 41 +++ scripts/hilightcmd.pl | 38 +++ scripts/rtm.pl | 77 ++++++ scripts/socket-interface.pl | 246 +++++++++++++++++ 11 files changed, 2123 insertions(+) create mode 100644 scripts/anames.pl create mode 100644 scripts/automode.pl create mode 100644 scripts/bitlbee_autoreply.pl create mode 100644 scripts/bitlbee_html.pl create mode 100644 scripts/bitlbee_status_notice.pl create mode 100644 scripts/bitlbee_typing_notice.pl create mode 100644 scripts/grumble.pl create mode 100644 scripts/hilight_notice.pl create mode 100644 scripts/hilightcmd.pl create mode 100644 scripts/rtm.pl create mode 100644 scripts/socket-interface.pl diff --git a/scripts/anames.pl b/scripts/anames.pl new file mode 100644 index 0000000..b73203c --- /dev/null +++ b/scripts/anames.pl @@ -0,0 +1,233 @@ +# anames.pl +# Irssi script that adds an /anames command, a clone of /names, with away nicks +# grayed out. +# +# Thanks to Dirm and Chris62vw for the Perl help and coekie for writing the +# evil code to sort the nicklist by the alphabet and rank in nicklist.pl +# +# 1.5 - Fixed halfop display bug (patch by epinephrine), 20100712 +# +# 1.4 - Merged changes from VMiklos and readded /who redirection to prevent +# spamming the status window. - ms, 20090122 +# +# 1.3 - by VMiklos +# Doing /dowho is very annoying and /alias foo /dowho;/anames won't +# work either since anames will work from the old infos. So I've +# modified /anames to just do a /dowho and the nicklist will be printed +# when we'll get the answer from the server. +# +# 1.2 - It seems that redirected events will not pass through the internal +# mechanisms that update user information (like away states). So, it +# /dowho and the periodic execution of the command has been disabled. +# /anames will still work, but new away information will need to be +# obtained by executing a /who on a channel. +# If you can make redirection (execute a /who without the information +# spilling to the status window) work, let me know so I can fix the +# script. +# +# 1.0.1 - Fixed row-determining and max-nick-length code, changed command_add +# calls to refs instead of names. +# +# 1.0 - Added timer for periodic /who of all channels +# +# 0.9 - Initial test release + +use strict; +use Irssi; +use POSIX; +#use Data::Dumper; + +use vars qw($VERSION %IRSSI); + +$VERSION = '1.5'; +%IRSSI = ( + authors => 'Matt "f0rked" Sparks, Miklos Vajna', + contact => 'ms+irssi@quadpoint.org', + name => 'anames', + description => 'a /names display with away nicks colored', + license => 'GPLv2', + url => 'http://quadpoint.org', + changed => '2010-07-12', +); + +# How often to do a /who of all channels (in seconds) +#my $who_timer = 300; + +my $tmp_server; +my $tmp_chan; + + +sub cmd_anames +{ + my($args, $server, $item) = @_; + my $channel = Irssi::active_win->{active}; + $tmp_server = $server; + $tmp_chan = $channel->{"name"}; + + if ($args ne "") { + $server = $args; + $server =~ s/-([^ ]*) .*/\1/; + $tmp_server = Irssi::server_find_tag($server); + $tmp_chan = $args; + $tmp_chan =~ s/-[^ ]* (.*)/\1/; + } + + # set up redirection + $tmp_server->redirect_event("who", 1, $tmp_chan, 0, undef, + { + "event 352" => "redir who_reply", + "event 315" => "redir who_reply_end", + }); + + $tmp_server->command("who $tmp_chan"); +} + + +sub print_anames +{ + my $server = $tmp_server; + my $chan = $tmp_chan; + my $channel = Irssi::Server::channel_find($server, $chan); + my $nick; + + if (!$channel) { + # no nicklist + Irssi::print("Not joined to any channel", MSGLEVEL_CLIENTERROR); + } else { + # Loop through each nick and display + my @nicks; + my($ops, $halfops, $voices, $normal, $away) = (0, 0, 0, 0, 0); + + # sorting from nicklist.pl + foreach my $nick (sort {(($a->{'op'}?'1':$a->{'halfop'}?'2':$a->{'voice'}?'3':'4').lc($a->{'nick'})) + cmp (($b->{'op'}?'1':$b->{'halfop'}?'2':$b->{'voice'}?'3':'4').lc($b->{'nick'}))} $channel->nicks()) { + my $realnick = $nick->{'nick'}; + my $gone = $nick->{'gone'}; + + my $prefix; + if ($nick->{'op'}) { + $prefix = "@"; + $ops++; + } elsif ($nick->{'halfop'}) { + $prefix = "%%"; + $halfops++; + } elsif ($nick->{'voice'}) { + $prefix = "+"; + $voices++; + } else { + $prefix = " "; + $normal++; + } + + $prefix = "%W$prefix%n"; + if ($gone) { + $realnick = "%K$realnick%n"; + $away++; + } + + push @nicks, "$prefix" . $realnick; + } + + my $total = @nicks; + $channel->print("%K[%n%gUsers%n %G" . $chan . "%n%K]%n", + MSGLEVEL_CLIENTCRAP); + columnize_nicks($channel,@nicks); + $channel->print("%W$chan%n: Total of %W$total%n nicks %K[%W$ops%n ops, " . + "%W$halfops%n halfops, %W$voices%n voices, %W$normal%n " . + "normal, %W$away%n away%K]%n", + MSGLEVEL_CLIENTNOTICE); + } +} + + +# create a /names style column, increasing alphabetically going down the +# columns. +sub columnize_nicks +{ + my($channel, @nicks) = @_; + my $total = @nicks; + + # determine max columns + # FIXME: this could be more intelligent (i.e., read window size) + my $cols = Irssi::settings_get_int("names_max_columns"); + $cols = 6 if $cols == 0; + + # determine number of rows + my $rows = round(ceil($total / $cols)); + + # array of rows + my @r; + for (my $i = 0; $i < $cols; $i++) { + # peek at next $rows items, determine max length + my $max_length = find_max_length(@nicks[0 .. $rows - 1]); + + # fill rows + for (my $j = 0; $j < $rows; $j++) { + my $n = shift @nicks; # single nick + if ($n ne "") { + $r[$j] .= "%K[%n$n" . fill_spaces($n,$max_length) . "%K]%n "; + } + } + } + + for (my $m = 0; $m < $rows; $m++) { + chomp $r[$m]; + $channel->print($r[$m], MSGLEVEL_CLIENTCRAP); + } +} + + +sub fill_spaces +{ + my($text, $max_length) = @_; + $text =~ s/%[a-zA-Z]//g; + return " " x ($max_length - length($text)); +} + + +sub find_max_length +{ + my $max_length = 0; + for (my $i = 0; $i < @_; $i++) { + my $nick = $_[$i]; + $nick =~ s/%[a-zA-Z]//g; + if (length($nick) > $max_length) { + $max_length = length($nick); + } + } + return $max_length; +} + + +sub round +{ + my($number) = @_; + return int($number + .5); +} + + +sub who_reply +{ + my($server, $data) = @_; + my(undef, $c, $i, $h, $n, $s) = split / /, $data; + if ($tmp_chan ne $c) { + $tmp_chan = $c; + #print "Got who info for $c"; + } +} + + +sub who_reply_end +{ + print_anames(); + $tmp_chan = ""; +} + + +Irssi::Irc::Server::redirect_register("who", 0, 0, + {"event 352" => 1}, + {"event 315" => 1}, + undef); +Irssi::signal_add("redir who_reply", \&who_reply); +Irssi::signal_add("redir who_reply_end", \&who_reply_end); +Irssi::command_bind("anames", \&cmd_anames); diff --git a/scripts/automode.pl b/scripts/automode.pl new file mode 100644 index 0000000..0e659bd --- /dev/null +++ b/scripts/automode.pl @@ -0,0 +1,321 @@ +# automode.pl +# +# Passively learn and actively maintain the ops/voices/halfops in channels. +# This is a no-maintenance auto-op/auto-voice script for irssi. +# +# INSTALL: +# 1) /script load automode.pl +# 2) Be a channel operator +# +# HOW IT WORKS: +# When someone joins a channel and is given ops/voice/halfop, the script +# will record that user's mask, as a combination of their nickname and part +# of their hostname or IP address. When that person leaves and rejoins, the +# script will check against its database and regrant the user the modes +# he or she had before leaving. +# +# If a user is kicked from a channel, all modes for that person are removed. +# They must therefore be regiven by another operator manually. Note this. +# +# Also, this script relies on the "chatnet" attribute being set for a +# particular connection. Use /network (or /ircnet) to set up your networks +# and such. This script will spit out (lots of) warnings if the chatnet is +# not set. +# +# IGNORING CHANNELS: +# If you do not wish to maintain modes on a channel, add it to the setting +# "automode_ignore" in the form :, separated by spaces. +# +# For example: /set automode_ignore FreeNode:#perl EFnet:#irssi +# (should) not maintain modes in #perl on FreeNode or #irssi +# on EFnet, provided FreeNode and EFnet are the tags for those +# connections. +# +# NOTES: +# The Perl module Data::Serializer is needed for this script. +# The database file is not written instantaneously; it is on a timer and is +# written to every five minutes or so. If the script is reloaded before it has +# had a chance to save will result in forgotten modes. +# +use strict; +use Irssi; +use Data::Serializer; +use Data::Dumper; + +use vars qw($VERSION %IRSSI); + +$VERSION = '1.3'; +%IRSSI = ( + authors => 'Matt "f0rked" Sparks', + contact => 'ms+irssi@quadpoint.org', + name => 'automode', + description => 'Mode maintainer', + license => 'BSD', + url => 'http://quadpoint.org', + changed => '2008-06-14', +); + +# show debug lines +my $debug = 0; + +my $s = new Data::Serializer; +my $file = Irssi::get_irssi_dir."/automode_list"; + +if (!-e $file) { + print "[automode] creating $file"; + system("touch $file"); +} + +my $listref = $s->retrieve($file); +my %list = $listref ? %{$listref} : (); + +#print Dumper %list; + +my $save_tag; +my %buffer_tags; +my %buffer; + + +sub save_list +{ + $s->store(\%list,$file); +} + + +sub clear_list +{ + %list = (); +} + + +sub make_mask +{ + my($address) = @_; + return if !$address; + my($ident, $host) = split /@/, $address; + my @split = split /\./, $host; + + if (@split <= 2) { + # host is something like "foo.com". We cannot make the mask *.com. + } else { + if ($split[$#split] =~ /^\d+$/) { + # Looks like an IP address. + pop @split; + $host = join(".", @split) . ".\d{1,3}"; + } else { + # Mask the first segment. + shift @split; + $host = ".+?." . join(".", @split); + } + } + + return ".+?!.*${ident}@" . "${host}"; +} + + +sub show +{ + my($net, $channel) = @_; + print Dumper %{$list{$net}->{$channel}}; +} + + +sub show_all +{ + my $list; + print Dumper %list; +} + + +sub clear_channel +{ + my($net, $channel) = @_; + delete $list{$net}->{$channel}; +} + + +sub set_modes +{ + my($net, $channel) = @{$_[0]}; + return if !$buffer{$net}->{$channel}; + + my($nicks, $modes) = values(%{$buffer{$net}->{$channel}}); + print "[automode] modes: $modes, nicks: $nicks" if $debug; + my $c = Irssi::server_find_chatnet($net)->channel_find($channel); + + # iterate through the modes and see which ones we don't have to set + my($final_modes, $final_nicks); + + my $i = 0; + for (split //,$modes) { + my $m = $_; + my $n = (split / /, $nicks)[$i]; + $i++; + + next if (!$c->nick_find($n)); + next if ($m eq "o" && $c->nick_find($n)->{"op"}); + next if ($m eq "v" && $c->nick_find($n)->{"voice"}); + next if ($m eq "h" && $c->nick_find($n)->{"halfop"}); + + # if we made it this far, add this to the final modes + $final_modes .= $m; + $final_nicks .= "$n "; + } + + print "[automode] final modes: +$final_modes $final_nicks" if $debug; + + $c->command("MODE $channel +$final_modes $final_nicks") + if ($final_modes && $final_nicks); + delete $buffer{$net}->{$channel}; +} + + +sub mode2letter +{ + my($mode) = @_; + if ($mode eq "@") { + return "o"; + } elsif ($mode eq "+") { + return "v"; + } elsif ($mode eq "%") { + return "h"; + } + return -1; +} + + +sub remove_mode +{ + my($net, $channel, $mask, $mode) = @_; + my $letter = mode2letter($mode); + $list{$net}->{$channel}->{$mask} =~ s/$letter// + if user_modes($net, $channel, $mask); + delete $list{$net}->{$channel}->{$mask} + if exists $list{$net}->{$channel}->{$mask} + and !$list{$net}->{$channel}->{$mask}; +} + + +sub remove_all +{ + my($net, $channel, $mask) = @_; + delete $list{$net}->{$channel}->{$mask} + if exists $list{$net}->{$channel}->{$mask}; +} + + +sub user_modes +{ + my($net, $channel, $mask) = @_; + return $list{$net}->{$channel}->{$mask}; +} + + +sub add_mode +{ + my($net, $channel, $mask, $mode) = @_; + return if !$mask or !$net or !$channel or !$mode; + + my $letter = mode2letter($mode); + $list{$net}->{$channel}->{$mask} .= $letter + if $list{$net}->{$channel}->{$mask} !~ /$letter/; + + Irssi::timeout_remove($save_tag); + $save_tag = Irssi::timeout_add_once(300, "save_list", []); +} + + +sub event_mode +{ + my($channel, $nick, $setby, $mode, $type) = @_; + return if check_ignore($channel->{server}, $channel->{name}); + my $w = Irssi::active_win; + return if $mode != '@' and $mode != '%' and $mode != '+'; + + my $chatnet = $channel->{server}->{chatnet}; + my $tag = $channel->{server}->{tag}; + print ("[automode] The 'chatnet' attribute is missing for the tag '$tag'. " . + "Use /network (or /ircnet) to properly manage this.") if !$chatnet; + return if !$chatnet; + + my $mask = make_mask($nick->{host}); + print "[automode] failed to make mask ($mask)" if (!$mask && $debug); + return if !$mask; + + if ($type eq "+") { + print ("[automode] adding mode '$mode' for $mask in $channel->{name} on " . + $chatnet) if $debug; + add_mode($chatnet, $channel->{name}, $mask, $mode); + } else { + # don't remove op if they deop themselves. + return if $setby eq $nick->{nick}; + print ("[automode] removing mode '$mode' for $mask in $channel->{name} " . + " on $chatnet") if $debug; + remove_mode($chatnet, $channel->{name}, $mask, $mode); + } + + #show($chatnet, $channel->{name}); +} + + +sub event_join +{ + my($server, $channel, $nick, $address) = @_; + return if check_ignore($server, $channel); + my $mask = make_mask($address); + return if !user_modes($server->{chatnet}, $channel, $mask); + my $c = $server->channel_find($channel); + return if not $c->{chanop}; + + if (my $modes = user_modes($server->{chatnet}, $channel, $mask)) { + print "[automode] Matched mask ($mask) with modes: $modes" if $debug; + my $nick_list = "$nick " x length($modes); + my %buf = ($buffer{$server->{chatnet}}->{$channel} ? + %{$buffer{$server->{chatnet}}->{$channel}} : ()); + $buf{modes} .= $modes; + $buf{nicks} .= $nick_list; + $buffer{$server->{chatnet}}->{$channel} = \%buf; + my $tag = $server->{chatnet} . "_$channel"; + Irssi::timeout_remove($buffer_tags{$tag}); + $buffer_tags{$tag} = Irssi::timeout_add_once(1000 + int(rand(250) * 3), + "set_modes", + [$server->{chatnet}, + $channel]); + #print Dumper %buffer; + } +} + + +sub event_kick +{ + my($server, $channel, $nick, $kicker, $address, $reason) = @_; + my $n = $server->channel_find($channel)->nick_find($nick); + #print Dumper $n; + my $mask = make_mask($n->{host}); + remove_all($server->{chatnet}, $channel, $mask) if $mask; +} + + +sub check_ignore +{ + my($server, $channel) = @_; + my $chatnet = $server->{chatnet}; + my $ignore = Irssi::settings_get_str("automode_ignore") . " "; + return ($ignore =~ /$chatnet:$channel /i) ? 1 : 0; +} + + +# I don't think this does what I want it to do. +sub event_exit +{ + save_list; +} + + +Irssi::signal_add("gui exit", "event_exit"); + +Irssi::signal_add("message kick", "event_kick"); +Irssi::signal_add("message join", "event_join"); +Irssi::signal_add("nick mode changed", "event_mode"); + +Irssi::settings_add_str("automode", "automode_ignore", "IM:&bitlbee"); diff --git a/scripts/bitlbee_autoreply.pl b/scripts/bitlbee_autoreply.pl new file mode 100644 index 0000000..72e5d6b --- /dev/null +++ b/scripts/bitlbee_autoreply.pl @@ -0,0 +1,62 @@ +# Sends autoreplies to IM users when they message while you are away. +# +# SETTINGS +# [bitlbee] +# bitlbee_autoreply_duration = OFF +# -> Send how long you have been away in your auto-reply. This requires +# Time::Duration. +# Example auto-reply: "gone (away: 3 minutes and 2 seconds)" +use strict; +use Irssi; +use Time::Duration qw/duration_exact/; + +use vars qw($VERSION %IRSSI); + +$VERSION = '0.12'; +%IRSSI = ( + authors => 'Matt "f0rked" Sparks', + contact => 'ms+irssi@quadpoint.org', + name => 'bitlbee status notice', + description => 'Sends autoreplies to IM users while you are away', + license => 'GPLv2', + url => 'http://quadpoint.org', + changed => '2005-12-04', +); + +my $bitlbee_channel = "&bitlbee"; +my $bitlbee_server_tag = "IM"; + +my(%times, $away, $away_time); + + +sub away +{ + $away_time=time; + %times=(); +} + + +sub event_msg +{ + my($server, $msg, $nick, $address, $target) = @_; + return if $server->{tag} ne $bitlbee_server_tag; + return unless $server->{usermode_away}; + #return unless $address =~ /\@login\.oscar\.aol\.com$/; # Only send for AIM. + return unless !$target or ($target eq $bitlbee_channel and $nick ne "root"); + return unless time - $times{$nick} > 3600; # send an auto-reply once an hour. + + $times{$nick} = time; + my $append; + if (Irssi::settings_get_bool("bitlbee_autoreply_duration") && $away_time) { + $append = " (away: " . duration_exact(time - $away_time) . ")"; + } + + $server->command("/notice $nick $server->{away_reason}$append"); +} + + +Irssi::signal_add("message private", "event_msg"); +Irssi::signal_add("message public", "event_msg"); +Irssi::signal_add("away mode changed", "away"); + +Irssi::settings_add_bool("bitlbee", "bitlbee_autoreply_duration", 0); diff --git a/scripts/bitlbee_html.pl b/scripts/bitlbee_html.pl new file mode 100644 index 0000000..356b4de --- /dev/null +++ b/scripts/bitlbee_html.pl @@ -0,0 +1,460 @@ +# This script is a drop-in HTML filter and parser for use with bitlbee and +# irssi. HTML that can be rendered will be, and the rest will be stripped. +# This should not interfere with other scripts for bitlbee; it is intended to be +# a transparent mangler. +# +# TO USE: +# 1) Adjust the channel and server tag settings below. +# 2) Load the script +# 3) In the bitlbee control channel (&bitlbee), type: set strip_html false +# 4) Enjoy. +# +# For use with bitlbee 1.0+. ChangeLog is available at the bottom of the file. +# +# SETTINGS +# [bitlbee] +# bitlbee_replace_html = ON +# -> replaces HTML in incoming AIM messages +# bitlbee_replace_control_codes = OFF +# -> replaces control codes (bold, underline, reverse) in outgoing private +# messages to AIM users with HTML equivalents. This is turned off by +# default due to the known bug below. If this wouldn't bother you, and you +# would like to have this feature, there's no real harm in turning it on. +# bitlbee_strip_trailing_whitespace = OFF +# -> removes whitespace at the end of messages +# +# KNOWN BUGS +# * This script is somewhat incompatible with splitlong.pl. When long messages +# get split up, and bitlbee_replace_control_codes is ON, the control codes +# will properly get replaced when they are sent to the remote user, but when +# the subsequent split parts are displayed, they may contain the html that +# is supposed to be hidden. +use strict; +use Irssi::TextUI; +use Data::Dumper; + +use vars qw($VERSION %IRSSI); + +$VERSION = '0.95'; +%IRSSI = ( + authors => 'Matt "f0rked" Sparks', + contact => 'ms+irssi@quadpoint.org', + name => 'bitlbee_html', + description => 'Adds some HTML parsing to bitlbee messages', + license => 'GPLv2', + url => 'http://quadpoint.org', + changed => '2005-12-18', +); + +# TODO: Make these settings (?) +my $bitlbee_channel = "&bitlbee"; +my $bitlbee_server_tag = "IM"; + +# Time to wait to collect all parts of a message from bitlbee server +my $buffer_timeout = 200; # milliseconds + +# How long does a message have to be before we consider it a split? +my $split_length = 425; + +my %buffer; +my %emitting; +my $debug = 0; + + +# Thanks to timing (http://the-timing.nl) for this +Irssi::signal_add_last 'channel sync' => sub +{ + my($channel) = @_; + if ($channel->{topic} eq "Welcome to the control channel. Type \x02help\x02 for help information.") { + $bitlbee_server_tag = $channel->{server}->{tag}; + $bitlbee_channel = $channel->{name}; + } +}; + + +# Debug to the active window +sub adebug +{ + return if !$debug; + my($t) = @_; + my $w = Irssi::active_win(); + $w->print($t, MSGLEVEL_CLIENTCRAP); +} + + +# Debug to the status window +sub sdebug +{ + return if !$debug; + my($t) = @_; + print $t; +} + + +sub trim +{ + ($_) = @_; + s/^\s*(.+)\s*$/$1/; + return $_; +} + + +# return true if given address supports HTML, false if not. +sub does_html +{ + my($address) = @_; + return ($address =~ /^[a-z].*\@login\.oscar\.aol\.com$/i || + $address =~ /^[a-z].*\@login\.icq\.com$/i) ? 1 : 0; +} + + +# More advanced checking in the future? +sub bee_server +{ + my($server) = @_; + return 0 if !$server; + return ($server->{tag} ne $bitlbee_server_tag) ? 0 : 1; +} + + +sub html2irc +{ + ($_) = @_; + #print "Before: $_"; + + s/
/\n/isg; + s///isg; + s/<\/font>//ig; + s/<\/?body.*?>//isg; + s/<\/?html>//ig; + s/(.*?)<\/a>/handle_link($1,$2)/iesg; + + # note for ,, and , these are encapsulating tags, and they _may_ + # contain newlines. This script will split by newlines and emit message + # signals for each part, and a part may end up looking like "foo" with + # no end tag, because the end tag is on the next message. To fix this, we + # have to also check for foo\n and ^foo and put the control codes + # in the appropriate places to avoid formatting the wrong text. + s/(.*?)<\/b>/\002$1\002/ig; # bold + s/(.*?)\n/\002$1\n/isg; + s/^(.*?)<\/b>/\002$1\002/isg; + + s/(.*?)<\/u>/\037$1\037/ig; # underline + s/(.*?)\n/\037$1\n/isg; + s/^(.*?)<\/u>/\037$1\037/isg; + + s/(.*?)<\/i>/\026$1\026/ig; # reverse (for italics) + s/(.*?)\n/\026$1\n/isg; + s/^(.*?)<\/i>/\026$1\026/isg; + + s/"/"/ig; + s/<//ig; + s/&/&/ig; + + #print "After: $_"; + return $_; +} + + +sub irc2html +{ + ($_) = @_; + my $ret; + adebug "irc2html input: $_"; + + if (/^/) { + # Already htmlized? This shouldn't happen. Assume that html tags were + # manually inserted, in which case we should just escape the html and + # return. + s/&/&/g; + s/"/"/g; + s//>/g; + $ret = "$_"; + adebug "irc2html return: $ret"; + return $ret; + } + + my $orig = $_; + + s/&/&/g; + s/"/"/g; + s//>/g; + + s/\002(.*?)\002/$1<\/b>/sg; # bold + s/\002(.*)/$1<\/b>/sg; # unended bold + s/\037(.*?)\037/$1<\/u>/sg; # underline + s/\037(.*)/$1<\/u>/sg; + s/\026(.*?)\026/$1<\/i>/sg; # reverse (italics) + s/\026(.*)/$1<\/i>/sg; + s/\003/
/g; # newlines.. this is ctrl+c in irssi. + + #print "After: $_, length: ".length($_); + my $ret = ($_ ne $orig) ? "$_" : $orig; + adebug "irc2html return: $ret"; + return $ret; +} + + +sub handle_link +{ + my($url, $title) = @_; + $title = html2irc($title); + #adebug $url; + #adebug $title; + + if ($url eq $title) { + return $url; + } elsif ($url eq "mailto:$title") { + return $title; + } else { + my $str = "[$title]($url)"; + $str =~ s/^\[(\n)*/$1\[/; + return $str; + } +} + + +sub event_privmsg +{ + my($server, $msg, $nick, $address) = @_; + return if !Irssi::settings_get_bool("bitlbee_replace_html"); + return if !does_html($address); + return if $emitting{"priv_$nick"}; # don't catch if we're sending signal + return if !bee_server($server); + + #print "Received msg: $msg"; + # Check the buffer. If it is empty, set a timeout. Fill it in either + # case. This step is necessary because long messages will naturally + # get split up, and to avoid the HTML cutting split up, we buffer + # and then parse everything at once. + if (!$buffer{"priv_$nick"}) { + # We have no buffer, this is a new message + my @data = ("priv", $server, $nick, $address); + Irssi::timeout_add_once($buffer_timeout, "check_buffer", \@data); + } + + $msg .= " "; + sdebug "Adding part: '$msg'"; + $buffer{"priv_$nick"} .= $msg;#."\002|\002"; + + # Length hack. Sometimes we get multiple messages quickly from bitlbee + # that aren't html formatted with
. Detect this and add newlines + # appropriately. + my $length = length("$nick $address $msg"); + sdebug "Length: $length"; + if ($length < $split_length) { + $buffer{"priv_$nick"} .= "\n"; + } + + Irssi::signal_stop; +} + + +sub event_pubmsg +{ + my($server, $msg, $nick, $address, $target) = @_; + return if !Irssi::settings_get_bool("bitlbee_replace_html"); + return if !bee_server($server); + return if !does_html($address) and $nick ne "root"; # only aim does html. + return if $emitting{"pub_$nick"}; + + if (!$buffer{"pub_$nick"}) { + my @data = ("pub", $server, $nick, $address, $target); + Irssi::timeout_add_once($buffer_timeout, "check_buffer", \@data); + } + + $buffer{"pub_$nick"} .= $msg; + if (length("$nick $address $msg $target") < $split_length) { + $buffer{"pub_$nick"} .= "\n"; + } + + Irssi::signal_stop; +} + + +sub event_action +{ + my($server, $msg, $nick, $address, $target) = @_; + #print $msg; + return if !Irssi::settings_get_bool("bitlbee_replace_html"); + return if !does_html($address); + return if $emitting{"act_$nick"}; + return if !bee_server($server); + + if (!$buffer{"act_$nick"}) { + my @data = ("act", $server, $nick, $address, $target); + Irssi::timeout_add_once($buffer_timeout, "check_buffer", \@data); + } + + $buffer{"act_$nick"} .= $msg; + if (length("$nick $address $msg $target") < $split_length) { + $buffer{"act_$nick"} .= "\n"; + } + + Irssi::signal_stop; +} + + +sub event_send_text +{ + my($text, $server, $witem) = @_; + return if !bee_server($server); + return if !Irssi::settings_get_bool("bitlbee_replace_control_codes"); + + # This will make sure that the person we are talking to is on AIM. + my $address; + my $modified_text = $text; + if ($witem->{type} eq "CHANNEL") { + if (my($n, $s, $m) = $text =~ /^([^ ]+):([ ]*)(.*)$/) { + $address = $witem->nick_find($n)->{host}; + $modified_text = "$n:$s" . irc2html($m); + } + } else { + $address = $witem->{address}; + $modified_text = irc2html($text); + } + return if !does_html($address); + + #print Dumper $witem; + #print "Sending: $modified_text"; + Irssi::signal_stop; + Irssi::signal_continue($modified_text, $server, $witem); +} + + +sub event_send_command +{ + my($args, $server, $witem) = @_; + return if !bee_server($server); + adebug "send command: $args"; +} + + +sub event_message_own +{ + my($server, $msg, $target, $orig_target) = @_; + return if !bee_server($server); + return if !Irssi::settings_get_bool("bitlbee_replace_control_codes"); + return if !Irssi::settings_get_bool("bitlbee_replace_html"); + + if ($msg =~ /^/ || $msg =~ /^([^ ]+):[ ]*/) { + # If we're on the bitlbee server and the message looks like html, + # we're here. + #print "$server '$msg' $target $orig_target"; + + if ($orig_target && (my $qu = $server->query_find($target))) { + #print "orig target, found query."; + if ($qu->{address} && !does_html($qu->{address})) { + # The person we're talking to is not using an HTML-compatible + # protocol. Don't treat this as html. + return; + } + } elsif (!$orig_target && + (my $ch = $server->channel_find($target)) && + (my($t) = $msg =~ /^([^ ]+):/)) { + my $to_nick = $ch->nick_find($t); + if ($to_nick->{address} && !does_html($to_nick->{address})) { + # We're talking to someone in a particular channel on + # bitlbee, but they don't support HTML. + return; + } + } + + # If we made it this far, treat the message as HTML. + Irssi::signal_stop; + Irssi::signal_continue($server, html2irc($msg), $target, $orig_target); + } +} + + +sub check_buffer +{ + my($type, $server, $nick, $address, $target) = @{@_[0]}; + # We should now have a full buffer for $nick, we can examine it, parse + # it, and re-emit the appropriate signals without any HTML splitting + # problems. + my $msg = $buffer{"${type}_${nick}"}; + sdebug "Complete msg: $msg"; + return if !$msg; + $msg = html2irc($msg); + $msg =~ s/[\s]*$// + if Irssi::settings_get_bool("bitlbee_strip_trailing_whitespace"); + + # $msg is now in the appropriate format, we can emit the signal. + $emitting{"${type}_${nick}"} = 1; + my $sig = "message "; + if ($type eq "priv") { + $sig.="private"; + } elsif ($type eq "pub") { + $sig.="public"; + } else { + $sig.="irc action"; + } + #print "Emitting $sig"; + for (split /\n/, $msg) { + Irssi::signal_emit($sig, $server, $_, $nick, $address, $target); + } + + delete($buffer{"${type}_${nick}"}); + delete($emitting{"${type}_${nick}"}); +} + + +sub event_301 +{ + return if !Irssi::settings_get_bool("bitlbee_replace_html"); + my($server, $data, $foo, $bar)=@_; + return if !bee_server($server); + my($me, $them, @msg) = split / /, $data; + my $msg = join " ", @msg; + $msg = html2irc($msg); + $msg = ~s/[\s]*$// + if Irssi::settings_get_bool("bitlbee_strip_trailing_whitespace"); + Irssi::signal_stop; + Irssi::signal_continue($server, "$me $them $msg", $foo, $bar); +} + + +Irssi::signal_add_first("message private", "event_privmsg"); +Irssi::signal_add_first("message public", "event_pubmsg"); +Irssi::signal_add_first("event 301", "event_301"); +Irssi::signal_add_first("message irc action", "event_action"); + +Irssi::signal_add_first("send text", "event_send_text"); +Irssi::signal_add_first("send command", "event_send_command"); +Irssi::signal_add_first("message own_private", "event_message_own"); +Irssi::signal_add_first("message own_public", "event_message_own"); +Irssi::signal_add_first("message irc own_action", "event_message_own"); + +# Settings +Irssi::settings_add_bool("bitlbee", "bitlbee_replace_html", 1); +Irssi::settings_add_bool("bitlbee", "bitlbee_replace_control_codes", 0); +Irssi::settings_add_bool("bitlbee", "bitlbee_strip_trailing_whitespace", 0); + + +### ChangeLog ### +# Version 0.95 +# - fixed a small bug causing links to sometimes not be properly formatted +# Version 0.94 +# - fixed bug causing a script error when typing in the status window and on +# startup of irssi +# Version 0.93 +# - fixed a stupid case issue in address recognization. +# Version 0.92 +# - code cleanups +# - control codes are allowed in control channel, but targets must be used +# (remoteuser: I'm talking to you) (this is the default) +# Version 0.91 +# - more intelligent checking for HTML support +# - allowed html from AIM users over ICQ connections +# Version 0.9 +# - removed html parsing for non-AIM connections +# - added support for replacing control codes (created by ctrl+b, ctrl+v, and +# cltr+-) with html equivalents. Disabled by default. +# +# Version 0.8 (2005-12-02) +# - Initial public release + diff --git a/scripts/bitlbee_status_notice.pl b/scripts/bitlbee_status_notice.pl new file mode 100644 index 0000000..9b061e8 --- /dev/null +++ b/scripts/bitlbee_status_notice.pl @@ -0,0 +1,211 @@ +# bitlbee_status_notice.pl +# Adds detailed information about status changed to bitlbee query windows +# Information about known offline, online, and away durations will be printed +# to open query windows of buddies. Away messages will also be asked for when +# using the Oscar network. + +# To use: +# Set the correct values for $bitlbee_* below, and then: +# /script load bitlbee_status_notice.pl + +# Settings: +# /set bitlbee_hide_joins ON|OFF +# Prevents joins from showing up in #bitlbee control channel when buddies +# sign on +# /set bitlbee_hide_quits ON|OFF +# Same for buddies signing off, except it also applies to query windows, +# because Irssi shows quit notices in query windows automatically. +# +# As of version 1.4, these settings default to OFF. +# If you wish also to ignore mode changes (voicing/devoicing): +# /ignore &bitlbee MODES +use strict; +use Irssi; +use Time::Duration; +use Data::Dumper; + +use vars qw($VERSION %IRSSI); + +$VERSION = '1.4'; +%IRSSI = ( + authors => 'Matt "f0rked" Sparks', + contact => 'ms+irssi@quadpoint.org', + name => 'bitlbee_status_notice', + description => 'Adds detailed information about status changes to bitlbee query windows', + license => 'GPLv2', + url => 'http://quadpoint.org', + changed => '2010-03-04', +); + +my $bitlbee_channel = "&bitlbee"; +my $bitlbee_server_tag = "IM"; + +my %away_watch; +my %away_times; +my %online_times; +my %offline_times; + +my $hide_it; +my $requested_info; + +Irssi::theme_register([ + 'state_away', '{channick $0} {chanhost $1} has gone away', + 'state_back', '{channick_hilight $0} {chanhost_hilight $1} has come back$2', + 'away_msg', '{chanhost msg} $0', + 'join', '{channick_hilight $0} {chanhost_hilight $1} has signed on$2', + 'quit', '{channick $0} {chanhost $1} has signed off$2', +]); + + +Irssi::signal_add_last 'channel sync' => sub +{ + my($channel) = @_; + if ($channel->{topic} eq "Welcome to the control channel. Type \x02help\x02 for help information.") { + $bitlbee_server_tag = $channel->{server}->{tag}; + $bitlbee_channel = $channel->{name}; + } +}; + + +sub get_channel +{ + my @channels = Irssi::channels(); + foreach my $channel (@channels) { + if ($channel->{topic} eq "Welcome to the control channel. Type \x02help\x02 for help information.") { + $bitlbee_channel = $channel->{name}; + $bitlbee_server_tag = $channel->{server}->{tag}; + return 1; + } + } + return 0; +} + + +sub event_join +{ + my($server, $channel, $nick, $address) = @_; + if ($channel eq $bitlbee_channel && $server->{tag} eq $bitlbee_server_tag) { + my $off_time; + $off_time = time - $offline_times{$nick} if $offline_times{$nick}; + delete $offline_times{$nick} if $offline_times{$nick}; + + my $str; + $str = " (last seen: " . ago_exact($off_time) . ")" if $off_time; + $online_times{$nick} = time; + + my $window = $server->query_find($nick); + if ($window) { + $window->printformat(MSGLEVEL_JOINS, "join", $nick, $address, $str); + } + + Irssi::signal_stop() # don't print the join announcement in &bitlbee + if Irssi::settings_get_bool("bitlbee_hide_joins"); + } +} + + +sub event_quit +{ + my($server, $nick, $address, $reason) = @_; + if ($server->{tag} eq $bitlbee_server_tag) { + my $on_time; + $on_time = time - $online_times{$nick} if $online_times{$nick}; + delete $online_times{$nick} if $online_times{$nick}; + + my $str; + $str = " (duration: " . duration($on_time) . ")" if $on_time; + $offline_times{$nick} = time; + + my $window = $server->query_find($nick); + if ($window) { + $window->printformat(MSGLEVEL_QUITS, "quit", $nick, $address, $str); + } + + Irssi::signal_stop() # don't print the quit announcement anywhere + if Irssi::settings_get_bool("bitlbee_hide_quits"); + } +} + + +sub event_mode +{ + my($channel, $nick, $setby, $mode, $type) = @_; + #print Dumper $nick; + #print Dumper $channel; + if ($mode eq "+" && $channel->{name} eq $bitlbee_channel && + $channel->{server}->{tag} eq $bitlbee_server_tag) { + my $window = $channel->{server}->query_find($nick->{nick}); + my $gone_time; + + if ($type eq "-") { + $away_times{$nick->{nick}} = time; + } elsif ($type eq "+") { + if (my $time = $away_times{$nick->{nick}}) { + $gone_time = time-$time; + delete $away_times{$nick->{nick}}; + } + } + + if ($window) { + if ($type eq "+") { + my $gone_str; + $gone_str = " (gone: ".duration($gone_time).")" if $gone_time; + $window->printformat(MSGLEVEL_MODES, "state_back", $nick->{nick}, + $nick->{host}, $gone_str) + if (time-$online_times{$nick->{nick}} > 2); + } elsif ($type eq "-") { + $window->printformat(MSGLEVEL_MODES, "state_away", $nick->{nick}, + $nick->{host}); + if ($nick->{host} =~ /login\.oscar\.aol\.com$/) { + $away_watch{nick} = $nick->{nick}; + $channel->{server}->send_message($channel->{name}, + "info $nick->{nick}", 0); + $requested_info = 1; + } + } + } + } +} + + +sub pub_msg +{ + my($server, $msg, $nick, $address, $target) = @_; + #print "$msg $nick $address $target"; + if ($nick eq "root" && $server->{tag} eq $bitlbee_server_tag && + $target eq $bitlbee_channel) { + my $window = $server->channel_find($target); + if ($window) { + my $qwin; + $qwin = $server->query_find($away_watch{nick}) if $away_watch{nick}; + if ($msg =~ /^TOC\(?.*\)? \- Away Message/g || + $msg =~ /^oscar \- Away Message/) { + $away_watch{watch} = 1; + $hide_it = 1 if $requested_info; + Irssi::timeout_add_once(400, + sub { $hide_it = 0; $requested_info = 0; }, + ""); + #$qwin->print("Away message:",MSGLEVEL_CRAP) if $qwin; + } elsif ($msg =~ /^TOC\(?.*\)? \- .+$/ || $msg =~ /^oscar \- .+$/) { + delete $away_watch{watch}; + delete $away_watch{nick}; + } elsif ($away_watch{watch} && $qwin) { + $qwin->printformat(MSGLEVEL_CRAP, "away_msg", $msg) if $qwin; + } + } + + Irssi::signal_stop if $hide_it; + } +} + + +get_channel(); + + +Irssi::settings_add_bool("bitlbee", "bitlbee_hide_joins", 0); +Irssi::settings_add_bool("bitlbee", "bitlbee_hide_quits", 0); + +Irssi::signal_add("message public", "pub_msg"); +Irssi::signal_add("nick mode changed", "event_mode"); +Irssi::signal_add("message join", "event_join"); +Irssi::signal_add("message quit", "event_quit"); diff --git a/scripts/bitlbee_typing_notice.pl b/scripts/bitlbee_typing_notice.pl new file mode 100644 index 0000000..bf42c88 --- /dev/null +++ b/scripts/bitlbee_typing_notice.pl @@ -0,0 +1,319 @@ +# INSTALLATION +# [&bitlbee] set typing_notice true +# <@root> typing_notice = `true' +# AND +# /statusbar window add typing_notice +# +# SETTINGS +# [bitlbee] +# bitlbee_send_typing = ON +# -> send typing messages to buddies +# bitlbee_typing_allwin = OFF +# -> show typing notifications in all windows +# +# +# Changelog: +# +# 2006-11-02 (version 1.6.1_ +# * Sending typing works again. +# +# 2006-10-27 (version 1.6) +# * 'channel sync' re-implemented. +# * bitlbee_send_typing was a string setting, It's a boolean now, like it should. +# +# 2006-10-24 (version 1.5) +# * Sending notices to online users only. +# * Using the new get_channel function; +# +# 2005-12-15 (version 1.42): +# * Fixed small bug with typing notices disappearing under certain circumstances +# in channels +# * Fixed bug that caused outgoing notifications not to work +# * root cares not about our typing status. +# +# 2005-12-04 (version 1.41): +# * Implemented stale states in statusbar (shows "(stale)" for OSCAR connections) +# * Introduced bitlbee_typing_allwin (default OFF). Set this to ON to make +# typing notifications visible in all windows. +# +# 2005-12-03 (version 1.4): +# * Major code cleanups and rewrites for bitlbee 1.0 with the updated typing +# scheme. TYPING 0, TYPING 1, and TYPING 2 are now supported from the server. +# * Stale states (where user has typed in text but has stopped typing) are now +# recognized. +# * Bug where user thinks you are still typing if you close the window after +# typing something and then erasing it quickly.. fixed. +# * If a user signs off while they are still typing, the notification is removed +# This update by Matt "f0rked" Sparks +# +# 2005-08-26: +# Some fixes for AIM, Thanks to Dracula. +# +# 2005-08-16: +# AIM supported, for sending notices, using CTCP TYPING 0. (Use the AIM patch from Hanji http://get.bitlbee.org/patches/) +# +# 2004-10-31: +# Sends typing notice to the bitlbee server when typing a message in irssi. bitlbee > 0.92 +# +# 2004-06-11: +# shows [typing: ] in &bitlbee with multiple users. +# +use strict; +use Irssi; +use Irssi::TextUI; + +use vars qw($VERSION %IRSSI); + +$VERSION = '1.6.1'; +%IRSSI = ( + authors => 'Tijmen "timing" Ruizendaal, Matt "f0rked" Sparks', + contact => 'tijmen.ruizendaal@gmail.com, ms+irssi@quadpoint.org', + name => 'BitlBee_typing_notice', + description => '1. Adds an item to the status bar wich shows [typing] when someone is typing a message on the supported IM-networks + 2. Sending typing notices to the supported IM networks (the other way around)', + license => 'GPLv2', + url => 'http://the-timing.nl/stuff/irssi-bitlbee, http://quadpoint.org', + changed => '2006-11-02', +); + +my $bitlbee_channel = "&bitlbee"; +my $bitlbee_server_tag = "localhost"; + +my $KEEP_TYPING_TIMEOUT = 1; +my $STOP_TYPING_TIMEOUT = 7; # How often to check if we are typing, or on msn, + # how long to keep the typing notice up, or check + # if the other user is still typing... + +my %timer_tag; + +my %typing; +my %tag; +my $line; +my %out_typing; +my $lastkey; +my $keylog_active = 1; +my $command_char = Irssi::settings_get_str('cmdchars'); +my $to_char = Irssi::settings_get_str("completion_char"); + +get_channel(); + +Irssi::signal_add_last 'channel sync' => sub { + my($channel) = @_; + if ($channel->{topic} eq "Welcome to the control channel. Type \x02help\x02 for help information.") { + $bitlbee_server_tag = $channel->{server}->{tag}; + $bitlbee_channel = $channel->{name}; + } +}; + +sub get_channel { + my @channels = Irssi::channels(); + foreach my $channel(@channels) { + if ($channel->{topic} eq "Welcome to the control channel. Type \x02help\x02 for help information.") { + $bitlbee_channel = $channel->{name}; + $bitlbee_server_tag = $channel->{server}->{tag}; + return 1; + } + } + return 0; +} + +sub event_ctcp_msg { + my ($server, $msg, $from, $address) = @_; + #print "CTCP: $msg $from $address"; + return if $server->{tag} ne $bitlbee_server_tag; + if (my($type) = $msg =~ "TYPING ([0-9])") { + Irssi::signal_stop(); + if ($type == 0) { + unset_typing($from); + } + elsif ($type == 1) { + $typing{$from}=1; + if ($address !~ /\@login\.oscar\.aol\.com/ + and $address !~ /\@YAHOO/ + and $address !~ /\@login\.icq\.com/) { + #Irssi::timeout_remove($tag{$from}); + #$tag{$from}=Irssi::timeout_add_once($STOP_TYPING_TIMEOUT*1000,"unset_typing",$from); + } + redraw($from); + } + elsif ($type == 2) { + stale_typing($from); + } + } +} + +sub unset_typing { + my($from, $no_redraw)=@_; + delete $typing{$from} if $typing{$from}; + Irssi::timeout_remove($tag{$from}); + redraw($from) if !$no_redraw; +} + +sub stale_typing { + my($from) = @_; + $typing{$from} = 2; + redraw($from); +} + +sub redraw { + my($from)=@_; + my $window = Irssi::active_win(); + my $channel = $window->get_active_name(); + if ($from eq $channel || $channel eq $bitlbee_channel + || $channel =~ /&chat_0/ + || Irssi::settings_get_bool("bitlbee_typing_allwin")) { + Irssi::statusbar_items_redraw('typing_notice'); + } +} + +sub event_msg { + my ($server,$data,$from,$address,$target) = @_; + return if $server->{tag} ne $bitlbee_server_tag; + my $channel=Irssi::active_win()->get_active_name(); + unset_typing $from, "no redraw"; + unset_typing $channel; +} + +sub event_quit { + my($server,$nick,$address,$reason)=@_; + return if $server->{tag} ne $bitlbee_server_tag; + unset_typing $nick; +} + +sub typing_notice { + my ($item, $get_size_only) = @_; + my $window = Irssi::active_win(); + my $channel = $window->get_active_name(); + + if (exists($typing{$channel})) { + my $append=$typing{$channel}==2 ? " (stale)" : ""; + $item->default_handler($get_size_only, "{sb typing$append}", 0, 1); + } + else { + $item->default_handler($get_size_only, "", 0, 1); + Irssi::timeout_remove($tag{$channel}); + } + if ($channel eq $bitlbee_channel || $channel =~ /&chat_0/ + || Irssi::settings_get_bool("bitlbee_typing_allwin")) { + foreach my $key (keys(%typing)) { + $line .= " ".$key; + if ($typing{$key}==2) { $line .= " (stale)"; } + } + if ($line ne "") { + $item->default_handler($get_size_only, "{sb typing:$line}", 0, 1); + $line = ""; + } + } +} + +sub empty { + my $from = shift; + delete($typing{$from}); + Irssi::statusbar_items_redraw('typing_notice'); +} + +sub window_change { + Irssi::statusbar_items_redraw('typing_notice'); + my $win = !Irssi::active_win() ? undef : Irssi::active_win()->{active}; + if (ref $win && ($win->{server}->{tag} eq $bitlbee_server_tag)) { + if (!$keylog_active) { + $keylog_active = 1; + Irssi::signal_add_last('gui key pressed', 'key_pressed'); + #print "Keylog started"; + } + } + else { + if ($keylog_active) { + $keylog_active = 0; + Irssi::signal_remove('gui key pressed', 'key_pressed'); + #print "Keylog stopped"; + } + } +} + +sub key_pressed { + return if !Irssi::settings_get_bool("bitlbee_send_typing"); + my $key = shift; + if ($key != 9 && $key != 10 && $lastkey != 27 && $key != 27 + && $lastkey != 91 && $key != 126 && $key != 127) + { + my $server = Irssi::active_server(); + my $window = Irssi::active_win(); + my $nick = $window->get_active_name(); + if ($server->{tag} eq $bitlbee_server_tag && + $nick ne "(status)" && + $nick ne "root") + { + if ($nick eq $bitlbee_channel) { + my $input = Irssi::parse_special("\$L"); + my ($first_word) = split(/ /,$input); + if ($input !~ /^$command_char.*/ && $first_word =~ s/$to_char$//){ + send_typing($first_word); + } + } + else { + my $input = Irssi::parse_special("\$L"); + if ($input !~ /^$command_char.*/ && length($input) > 0){ + send_typing($nick); + } + } + } + } + $lastkey = $key; +} + +sub out_empty { + my ($a) = @_; + my($nick,$tag)=@{$a}; + delete($out_typing{$nick}); + #print $winnum."|".$nick; + if (my $server=Irssi::server_find_tag($tag)) { + $server->command("^CTCP $nick TYPING 0"); + } +} + +sub send_typing { + my $nick = shift; + if (!exists($out_typing{$nick}) || time - $out_typing{$nick} > $KEEP_TYPING_TIMEOUT) { + my @nicks = Irssi::server_find_tag($bitlbee_server_tag)->channel_find($bitlbee_channel)->nicks(); + my $exists=0; + foreach my $nick1(@nicks) { #check if the nickname is in the BitlBee channel + if($nick1->{'nick'} eq $nick) { + #print "Exists!"; + $exists=1; + } + } + if (!$exists) { + #print "Does not exist"; + return; + } + + #print "Send typing"; + my $server = Irssi::active_server(); + $server->command("^CTCP $nick TYPING 1"); + + $out_typing{$nick} = time; + + ### Reset 'stop-typing' timer + if ($timer_tag{$nick}) { + Irssi::timeout_remove($timer_tag{$nick}); + delete($timer_tag{$nick}); + } + $timer_tag{$nick} = Irssi::timeout_add_once($STOP_TYPING_TIMEOUT*1000, 'out_empty', ["$nick", $server->{tag}]); + } +} + +# README: Delete the old bitlbee_send_typing string from ~/.irssi/config. +# A boolean is better. + +Irssi::settings_add_bool("bitlbee","bitlbee_send_typing",1); +Irssi::settings_add_bool("bitlbee","bitlbee_typing_allwin",0); + +Irssi::signal_add("ctcp msg", "event_ctcp_msg"); +Irssi::signal_add("message private", "event_msg"); +Irssi::signal_add("message public", "event_msg"); +Irssi::signal_add("message quit", "event_quit"); +Irssi::signal_add_last('window changed', 'window_change'); +Irssi::signal_add_last('gui key pressed', 'key_pressed'); +Irssi::statusbar_item_register('typing_notice', undef, 'typing_notice'); +Irssi::statusbars_recreate_items(); diff --git a/scripts/grumble.pl b/scripts/grumble.pl new file mode 100644 index 0000000..5415d66 --- /dev/null +++ b/scripts/grumble.pl @@ -0,0 +1,115 @@ +# grumble.pl provides sane integration of Irssi with Growl [http://growl.info/] +# and Mumbles [http://www.mumbles-project.org/]. These programs both support the +# Growl network protocol that the Perl module Net::Growl uses to communicate. +# +# This script supports multiple targets, each with a potentially unique Growl +# password. +# +# SETTINGS +# Targets are specified in the "grumble_targets" setting: +# /set grumble_targets localhost:mypass +# sends messages to an instance running on localhost with password "mypass" +# /set grumble_targets localhost:mypass othermachine:otherpass +# sends messages to two separate machines with different passwords +# +# To turn on/off notifications while /away, toggle the setting +# 'grumble_notify_when_away'. Default: OFF. +# +# To enable/disable notifications for the active window, toggle the setting +# 'grumble_notify_for_active_window'. Default: ON. +use strict; +use Irssi; +use Net::Growl; +use IO::Socket::INET; + +use vars qw($VERSION %IRSSI); + +$VERSION = '1.3'; +%IRSSI = ( + authors => 'Matt "f0rked" Sparks', + contact => 'ms+irssi@quadpoint.org', + name => 'grumble', + description => 'Irssi integration with growl and mumbles', + license => 'BSD', + url => 'http://quadpoint.org', + changed => '2010-02-08' +); + + +sub send_growl +{ + my($host, $password, $title, $text) = @_; + my %addr = (PeerAddr => $host, + PeerPort => Net::Growl::GROWL_UDP_PORT, + Proto => "udp"); + my $s = IO::Socket::INET->new(%addr) || die "Could not create socket: $!\n"; + my $r = Net::Growl::RegistrationPacket->new(application => "grumble.pl", + password => $password); + $r->addNotification(); + print $s $r->payload(); + + # send a notification + my $p = Net::Growl::NotificationPacket->new(application => "grumble.pl", + title => $title, + description => $text, + priority => 2, + sticky => 'True', + password => $password); + print $s $p->payload(); + close $s; +} + + +sub send_to_all +{ + my($title, $text) = @_; + my @targets = split / /, Irssi::settings_get_str("grumble_targets"); + + for my $target (@targets) { + my($host, $pass) = split /:/, $target, 2; + send_growl($host, $pass, $title, $text); + } +} + + +sub event_privmsg +{ + my($server, $msg, $nick, $address, $target) = @_; + + my $active = Irssi::active_win(); + return if ($active->get_active_name() eq $nick && + !Irssi::settings_get_bool("grumble_notify_for_active_window")); + + return if ($server->{usermode_away} && + !Irssi::settings_get_bool("grumble_notify_when_away")); + + send_to_all("irssi: $nick", "new private message from $nick"); +} + + +sub event_printtext +{ + my ($dest, $text, $stripped) = @_; + my $server = $dest->{server}; + + my $active = Irssi::active_win(); + return if ($active->get_active_name() eq $dest->{window}->get_active_name() && + !Irssi::settings_get_bool("grumble_notify_for_active_window")); + + return if ($server->{usermode_away} && + !Irssi::settings_get_bool("grumble_notify_when_away")); + + # Run the command if we're hilighted + if (($dest->{level} & (MSGLEVEL_HILIGHT)) && + ($dest->{level} & MSGLEVEL_NOHILIGHT) == 0) { + send_to_all("irssi: hilight", $stripped); + } +} + + +Irssi::signal_add("message private", \&event_privmsg); +Irssi::signal_add("print text", \&event_printtext); + +Irssi::settings_add_str("grumble", "grumble_targets", "localhost:grwlpass"); +Irssi::settings_add_bool("grumble", "grumble_notify_when_away", 0); +Irssi::settings_add_bool("grumble", "grumble_notify_for_active_window", 1); diff --git a/scripts/hilight_notice.pl b/scripts/hilight_notice.pl new file mode 100644 index 0000000..777796f --- /dev/null +++ b/scripts/hilight_notice.pl @@ -0,0 +1,41 @@ +# hilight_notice.pl for irssi +# +# This script changes the message level for notices to the level used by private +# messages. Notices go to the status window by default, so when this script is +# loaded, the status window will be hilighted like a query window. +# +# Based off of active_notice.pl by Geert. + +use strict; +use Irssi; + +use vars qw($VERSION %IRSSI); + +$VERSION = '1.1'; +%IRSSI = ( + authors => 'Matt "f0rked" Sparks', + contact => 'ms+irssi@quadpoint.org', + name => 'hilight_notice', + description => 'hilight status window on notices', + license => 'GPLv2', + url => 'http://quadpoint.org', + changed => '2006-12-15', +); + + +sub hilight_notice +{ + my ($dest, $text, $stripped) = @_; + my $server = $dest->{server}; + + return if (!$server || !($dest->{level} & MSGLEVEL_NOTICES)); + + # Change the message level to level used by PRIVMSGs + my $witem = $server->window_item_find($dest->{target}); + $witem->print($text, MSGLEVEL_MSGS) if $witem; + Irssi::print($text, MSGLEVEL_MSGS) if !$witem; + Irssi::signal_stop(); +} + + +Irssi::signal_add("print text", "hilight_notice"); diff --git a/scripts/hilightcmd.pl b/scripts/hilightcmd.pl new file mode 100644 index 0000000..c46ec5a --- /dev/null +++ b/scripts/hilightcmd.pl @@ -0,0 +1,38 @@ +use strict; +use Irssi; + +use vars qw($VERSION %IRSSI); + +$VERSION = '1.0'; +%IRSSI = ( + authors => 'Matt "f0rked" Sparks', + contact => 'ms+irssi@quadpoint.org', + name => 'hilightcmd', + description => 'Executes command on hilight', + license => 'Public domain', + url => 'http://quadpoint.org', + changed => '2005-06-16', +); + +# Run the command when away? +my $run_cmd_when_away = 0; +my $cmd_to_run = "~/bin/notify.sh"; + + +sub sig_printtext +{ + my($dest, $text, $stripped) = @_; + my $server = $dest->{server}; + + # Do not run the command if we're not supposed to when away + return if ($server->{usermode_away} && !$run_cmd_when_away); + + # Run the command if we're hilighted + if (($dest->{level} & (MSGLEVEL_HILIGHT)) && + ($dest->{level} & MSGLEVEL_NOHILIGHT) == 0) { + system($cmd_to_run, $text); + } +} + + +Irssi::signal_add("print text", \&sig_printtext); diff --git a/scripts/rtm.pl b/scripts/rtm.pl new file mode 100644 index 0000000..d3327af --- /dev/null +++ b/scripts/rtm.pl @@ -0,0 +1,77 @@ +# rtm.pl - add new tasks to rememberthemilk.com +# +# Install: +# 1) /script load rtm +# 2) /set rtm_email +# 3) /save +# +# Usage: +# /rtm +# +# where: +# task name, e.g. "do laundry" +# Uses RTM's 'Smart Add' for due dates, priority, etc. +# +# Examples: +# Do the dishes for an hour, priority 2. +# /rtm do dishes =1 hour !2 +# +# Do homework, due tomorrow. +# /rtm homework ^tomorrow +# +# See also: +# http://www.rememberthemilk.com/services/email/ +use strict; +use Irssi; + +use vars qw($VERSION %IRSSI); + +$VERSION = '1.0'; +%IRSSI = ( + authors => 'Matt "f0rked" Sparks', + contact => 'ms+irssi@quadpoint.org', + name => 'rtm', + description => 'Add new tasks to rememberthemilk.com', + license => 'BSD', + url => 'http://quadpoint.org', + changed => '2010-12-20', +); + + +sub rtm_print +{ + my($text) = @_; + my $window = Irssi::active_win(); + my $b = chr(2); + $window->print("[${b}rtm${b}] $text", MSGLEVEL_CRAP); +} + + +sub add_task +{ + my($email, $text) = @_; + open SM, qq(| mail -s "$text" $email); + print SM "\n\n"; + close SM; +} + + +sub cmd_rtm +{ + my($data, $server, $witem) = @_; + my $email_addr = Irssi::settings_get_str("rtm_email"); + if (!$email_addr) { + rtm_print("your rememberthemilk.com email address is not set. ". + "Use /set rtm_email to set it. Don't forget ". + "to /save."); + return; + } + + add_task($email_addr, $data); + + rtm_print($data); +} + + +Irssi::command_bind("rtm", "cmd_rtm"); +Irssi::settings_add_str("rtm", "rtm_email", ""); diff --git a/scripts/socket-interface.pl b/scripts/socket-interface.pl new file mode 100644 index 0000000..2e7b7c7 --- /dev/null +++ b/scripts/socket-interface.pl @@ -0,0 +1,246 @@ +use strict; +use IO::Socket; +use Fcntl; +use Irssi; +use Time::Format qw(%strftime); +#use Data::Dumper; + +use vars qw($VERSION %IRSSI); + +$VERSION = '1.0.1'; +%IRSSI = ( + authors => 'Matt "f0rked" Sparks, Miklos Vajna', + contact => 'ms+irssi@quadpoint.org', + name => 'socket-interface', + description => 'provides an interface to irssi via unix sockets', + license => 'GPLv2', + url => 'http://quadpoint.org', + changed => '2009-01-22', +); + +my $socket = $ENV{"HOME"} . "/.irssi/socket"; + +# create the socket +unlink $socket; +my $server = IO::Socket::UNIX->new(Local => $socket, + Type => SOCK_STREAM, + Listen => 5) or die $@; + +# set this socket as nonblocking so we can check stuff without interrupting +# irssi. +nonblock($server); + + +# method to set a socket handle as nonblocking +sub nonblock +{ + my($fd) = @_; + my $flags = fcntl($fd, F_GETFL, 0); + fcntl($fd, F_SETFL, $flags | O_NONBLOCK); +} + + +# check the socket for data and act upon it +sub check_sock +{ + my $msg; + if (my $client = $server->accept()) { + $client->recv($msg, 1024); + #print "Got message: $msg" if $msg; + + if ($msg =~ /^activelog ?(\d*)$/) { + my $lines = ($1) ? $1 : 5; + #print "found lines: $lines"; + #print Dumper $win; + my $ref = get_active_refnum(); + my $name = get_active_name(); + my $tag = get_active_tag(); + my $fname = get_log_fname(get_active_tag(), get_active_name()); + my $log = tail_log($fname, $lines); + if (!$log) { + $log = "Could not open log"; + } + + chomp $log; + $client->send(">> $ref: $name ($tag)\n$log"); + } elsif ($msg eq "windowlist") { + # send back a list of the windows + my $out; + for (2 .. last_refnum()) { + $out .= ("$_: " . name_of($_) . " (".tag_of($_).") " . + level_of($_) . "\n"); + } + + chomp $out; + $client->send($out); + } elsif ($msg =~ /^switch (\d+)$/) { + $client->send(switch_to($1)); + } elsif ($msg =~ /^get_lines (\d+)$/) { + $client->send(get_lines($1)); + } elsif ($msg =~ /^send (.+)$/) { + $client->send(msg_active($1)); + } elsif ($msg =~ /^command (.+)$/) { + $client->send(command($1)); + } + } +} + + +# returns the name of the active window item. If there is no active window +# item, return the name of the window itself. +sub get_active_name +{ + my $win = Irssi::active_win(); + return $win->get_active_name(); +} + + +# returns the server tag of the active window item +sub get_active_tag +{ + my $win = Irssi::active_win(); + return ($win->{active}) ? $win->{active}->{server}->{tag} : ""; +} + + +# returns refnum of active window +sub get_active_refnum +{ + return (Irssi::active_win())->{refnum}; +} + + +# switches windows to the given refnum +sub switch_to +{ + my($refnum) = @_; + my $window = Irssi::window_find_refnum($refnum); + if ($window) { + $window->set_active(); + return 1; + } else { + return 0; + } +} + + +# gets the lines from a buffer of a window +sub get_lines +{ + my($refnum) = @_; + my $window = Irssi::window_find_refnum($refnum); + if ($window) { + my $view = $window->view; + my $line = $view->get_lines(); + my $ret = ""; + while (defined $line) { + $ret .= $line->get_text(0) . "\n"; + $line = $line->next(); + } + return $ret; + } + else { + return 0; + } +} + + +# return highest refnum +sub last_refnum +{ + return Irssi::windows_refnum_last(); +} + + +# name of given refnum's window +sub name_of +{ + my($refnum) = @_; + my $win = Irssi::window_find_refnum($refnum); + return $win->get_active_name(); +} + + +# tag of given refnum's window +sub tag_of +{ + my($refnum) = @_; + my $win = Irssi::window_find_refnum($refnum); + return ($win->{active}) ? $win->{active}->{server}->{tag} : ""; +} + + +# level of given refnum's window +sub level_of +{ + my($refnum) = @_; + my $win = Irssi::window_find_refnum($refnum); + return $win->{data_level}; +} + + +sub msg +{ + my($refnum, $text) = @_; + my $win = Irssi::window_find_refnum($refnum); + return unless $win; + if ($text =~ /^\//) { + # this is a command, don't prepend /msg * + $win->command($text); + } else { + $win->command("msg * $text"); + } + return 1; +} + + +sub command +{ + my($command) = @_; + if (my $s = Irssi::active_server()) { + $s->command($command); + } else { + Irssi::command($command); + } + return 1; +} + + +sub msg_active +{ + my($text) = @_; + return msg(get_active_refnum(), $text); +} + + +sub get_log_fname +{ + my($servertag, $name) = @_; + $name = lc($name); + my $log_path = Irssi::settings_get_str("autolog_path"); + + # fill in variables + my $log_file = $strftime{$log_path}; + $log_file =~ s/^~/$ENV{HOME}/; + $log_file =~ s/\$tag/$servertag/g; + $log_file =~ s/\$0/$name/g; + #print "log file: $log_file"; + + return $log_file; # hope this is filled in enough. +} + + +# return the last x lines of a given filename +sub tail_log +{ + my($filename, $lines) = @_; + $lines ||= 5; + #print "found $filename"; + return 0 if !-e $filename; + my $t = qx(tail -n $lines '$filename'); + chomp $t; + return $t; +} + + +my $timer = Irssi::timeout_add(250, \&check_sock, []);