# INSTALLATION
#
# - Add the statusbar item:
# /statusbar window add typing_notice
# You won't see anything until someone is typing.
#
# SETTINGS
#
# [typing_notice]
# send_typing = ON
# -> send typing notifications to supported users
#
# CHANGES
# 2008-01-11
# * threw out most bitlbee stuff
# * all XEP 0085 typing levels
# * working interop with 0085
# 2008-01-05
# * ctcp capab protocol - irssis clientinfo is buggy
# * renamed typing levels - inspired by XEP 0085
# * started to remove bitlbee stuff
# -----------------------------------------------
# fippoism starts
# this script is based on version 1.9.1 of
# http://the-timing.nl/Projects/Irssi-BitlBee/typing_notice.pl
# the plan is to get compat to xmpp xep 0085 at least
# and of course psyc legacy ctcp support :-)
# -----------------------------------------------
#
use strict;
use Irssi::TextUI;
use Data::Dumper;
use vars qw($VERSION %IRSSI);
$VERSION = '0.2';
%IRSSI = (
authors => 'Philipp "fippo" Hancke',
contact => 'fippo@goodadvice.pages.de',
name => 'typing_notice_psyc',
description => 'based on Tijmen\' typing notification script for bitlbee
1. Receiving typing notices: Adds an item to the status bar which says [typing] when someone is typing a message.
2. Sending typing notices: Sends CTCP TYPING messages to PSYC, XMPP and IRC users (If they support it)',
license => 'GPLv2',
url => 'http://www.psyced.org/',
changed => '2008-01-11',
);
my $debug = 0;
my %TIMEOUTS = (
"PAUSED" => 5 * 1000,
"INACTIVE" => 30 * 1000,
"GONE" => 180 * 1000,
);
my %typers; # for storage
my $line;
my $lastkey;
my $keylog_active = 1;
my $command_char = Irssi::settings_get_str('cmdchars');
my $to_char = Irssi::settings_get_str("completion_char");
## IRC only ##############
# this is used to append a non-printable control sequence to all messages
# quite a smart hack indeed - but i would prefer ctcp for it
my $o = "\cO";
my $oo = $o.$o;
##########################
sub get_current {
my $server = Irssi::active_server();
my $window = Irssi::active_win();
if ($server && $window) {
return ($server->{tag}, $window->get_active_name());
}
return undef;
}
sub event_ctcp_msg { # called for ctcp msg, not ctcp replies
my ($server, $msg, $from, $address) = @_;
$server = $server->{tag};
if ($msg =~ /TYPING (INACTIVE|PAUSED|COMPOSING|ACTIVE|GONE)/ ) {
if ( not $debug ) {
Irssi::signal_stop();
}
# if someone sends this, they usually support that stuff
$typers{$server}{$from}{capability} = 1;
$typers{$server}{$from}{typing_in} = $1;
Irssi::statusbar_items_redraw('typing_notice');
Irssi::signal_stop();
} elsif ( my($type) = $msg =~ /CAPAB TYPING/ ) {
if ($debug) {
print "capab typing query from $from.";
}
my $serverobj = Irssi::server_find_tag($server);
$serverobj->ctcp_send_reply("NOTICE $from :\001CAPAB TYPING\001");
Irssi::signal_stop();
}
}
sub event_ctcp_reply { # called for ctcp replies
my ($server, $msg, $from, $address) = @_;
if ( $msg =~ /CAPAB TYPING/ && exists( $typers{$server->{tag}}{$from} )) {
if ($debug) {
print "capab typing reply from $from.";
}
$typers{$server->{tag}}{$from}{capability} = 1;
Irssi::signal_stop();
}
}
sub unset_typing_in {
my ($a) = @_;
my ($server, $nick) = @{$a};
if ($debug) {
print "unset: $server, $nick";
}
$typers{$server}{$nick}{typing_in} = undef;
Irssi::timeout_remove($typers{$server}{$nick}{timer_tag_in});
Irssi::statusbar_items_redraw('typing_notice');
}
sub event_msg {
my ($server, $data, $nick, $address, $target) = @_;
$server = $server->{tag};
# haeh??? ist das eine art angehaengt als leeres ctcp
if ( $data =~ /$oo\z/ ) {
if ( not exists( $typers{$server}{$nick} ) ) {
$typers{$server}{$nick}{capability} = 1;
if ($debug) {
print "This user supports typing! $server, $nick";
}
}
} elsif (0) { # ah... this ensures that it stays valid
if ( exists( $typers{$server}{$nick} ) ) {
if ($debug) {
print "This user does not support typing anymore! $nick. splice: ";
}
delete $typers{$server}{$nick};
}
}
if ( 0 and exists( $typers{$server}{$nick} ) ) {
unset_typing_in( [$server, $nick] );
}
}
sub typing_notice { ## redraw statusbar item
my ($item, $get_size_only) = @_;
my ($server, $channel) = get_current();
return unless exists $typers{$server}{$channel};
if ( $typers{$server}{$channel}{typing_in} ne undef ) {
my $append = $typers{$server}{$channel}{typing_in};
$item->default_handler($get_size_only, "{sb typing $append}", 0, 1);
if ($debug >= 2) {
print "typing: $server, $channel.";
}
} else {
if ($debug) {
print "clear: $server, $channel ";
}
$item->default_handler($get_size_only, "", 0, 1);
if ($typers{$server}{$channel}{timer_tag_in} ne undef) {
Irssi::timeout_remove($typers{$server}{$channel}{timer_tag_in});
$typers{$server}{$channel}{timer_tag_in} = undef;
}
}
}
sub window_change {
Irssi::statusbar_items_redraw('typing_notice');
my ($server, $channel) = get_current();
if ( exists( $typers{$server}{$channel} ) ) {
if ( not $keylog_active ) {
$keylog_active = 1;
Irssi::signal_add_last('gui key pressed', 'key_pressed');
}
} else {
if ($keylog_active) {
$keylog_active = 0;
Irssi::signal_remove('gui key pressed', 'key_pressed');
}
}
return if not Irssi::settings_get_bool("send_typing");
# send INACTIVE?
}
sub window_close {
return if not Irssi::settings_get_bool("send_typing");
my ($server, $channel) = get_current();
if ( exists( $typers{$server}{$channel} ) and $typers{$server}{$channel}{state_out} ne "GONE" ) {
my $serverobj = Irssi::server_find_tag($server);
$serverobj->command("^CTCP $channel TYPING GONE");
$typers{$server}{$channel}{state_out} = "GONE";
}
}
sub window_open {
return if not Irssi::settings_get_bool("send_typing");
my ($item) = @_;
# look if we should disco
}
sub key_pressed {
return if not Irssi::settings_get_bool("send_typing");
my $key = shift;
if ($key == 9 && $key == 10 && $lastkey == 27 && $key == 27 && $lastkey == 91 && $key == 126 && $key == 127) { # ignore these keys
$lastkey = $key;
return 0;
}
my ($server, $channel) = get_current();
if ( exists( $typers{$server}{$channel} ) ) {
my $input = Irssi::parse_special("\$L");
if ($input !~ /^$command_char.*/ && length($input) > 0){
send_typing( $server, $channel );
}
}
$lastkey = $key; # some keys, like arrow-up, contain two events.
}
sub send_typing_pause {
return if not Irssi::settings_get_bool("send_typing");
my ($a) = @_;
my( $server, $nick ) = @{$a};
send_typing_update_state($server, $nick, "PAUSED");
Irssi::timeout_remove($typers{$server}{$nick}{timer_tag_out});
$typers{$server}{$nick}{timer_tag_out} = Irssi::timeout_add_once($TIMEOUTS{INACTIVE}, 'send_typing_inactive', [$server, $nick]);
}
sub send_typing_inactive {
return if not Irssi::settings_get_bool("send_typing");
my ($a) = @_;
my( $server, $nick ) = @{$a};
send_typing_update_state($server, $nick, "INACTIVE");
Irssi::timeout_remove($typers{$server}{$nick}{timer_tag_out});
$typers{$server}{$nick}{timer_tag_out} = Irssi::timeout_add_once($TIMEOUTS{GONE}, 'send_typing_gone', [$server, $nick]);
}
sub send_typing_gone {
return if not Irssi::settings_get_bool("send_typing");
my ($a) = @_;
my( $server, $nick ) = @{$a};
send_typing_update_state($server, $nick, "GONE");
Irssi::timeout_remove($typers{$server}{$nick}{timer_tag_out});
}
sub send_typing {
my ( $server, $nick ) = @_;
send_typing_update_state($server, $nick, "COMPOSING");
Irssi::timeout_remove($typers{$server}{$nick}{timer_tag_out});
$typers{$server}{$nick}{timer_tag_out} = Irssi::timeout_add_once($TIMEOUTS{PAUSED}, 'send_typing_pause', [$server, $nick]);
}
sub send_typing_update_state {
return if not Irssi::settings_get_bool("send_typing");
my ( $server, $nick, $state ) = @_;
if (not exists($typers{$server}{$nick}) or $typers{$server}{$nick}{capability} != 1) {
print Dumper(%typers);
print "(dont) send_typing_update_state to non-typer $server->$nick"; #if ($debug);
return 0;
}
if ($state eq $typers{$server}{$nick}{state_out}) {
print "(dont) send_typing_update_state: $state already known" if $debug;
return;
}
# FIXME: allowed state transitions could be checked here
if ($debug) {
print "$server: ctcp $nick typing $state";
}
my $serverobj = Irssi::server_find_tag($server);
if (!$serverobj) {
print "send typing update state: server not found";
return;
}
$serverobj->command("^CTCP $nick TYPING $state");
$typers{$server}{$nick}{state_out} = $state;
}
sub db_typing {
print "------ Typers -----\n".Dumper(%typers);
}
sub event_send_msg { # outgoing messages
my ($msg, $server, $window) = @_;
return unless $window and $window->{type} eq "QUERY";
my $nick = $window->{name};
if ($debug) {
print "send msg: $server->{tag}, $nick";
}
if ( exists($typers{$server->{tag}}{$nick}) and
$typers{$server->{tag}}{$nick}{capability} == 1) {
$typers{$server->{tag}}{$nick}{state_out} = "ACTIVE";
Irssi::timeout_remove($typers{$server->{tag}}{$nick}{timer_tag_out});
}
if (!exists( $typers{$server->{tag}}{$nick} ) ) {
if ($debug) {
print "send capa query to $nick.";
}
$typers{$server->{tag}}{$nick}{capability} = -1;
if (my $serverobj = Irssi::server_find_tag($server->{tag})) {
$serverobj->command("^CTCP $nick CAPAB TYPING");
}
}
if ( 0 and length($msg) > 0) {
# ist das eine art
$msg .= $oo;
}
Irssi::signal_stop();
Irssi::signal_remove('send text', 'event_send_msg');
Irssi::signal_emit('send text', $msg, $server, $window);
Irssi::signal_add_first('send text', 'event_send_msg');
}
# Command
Irssi::command_bind('db_typing','db_typing');
# Settings
Irssi::settings_add_bool("typing_notice","send_typing",1);
# IRC events
Irssi::signal_add_first("send text", "event_send_msg"); # Outgoing messages
Irssi::signal_add("ctcp msg", "event_ctcp_msg");
Irssi::signal_add("ctcp reply", "event_ctcp_reply");
Irssi::signal_add("message private", "event_msg");
Irssi::signal_add("message public", "event_msg");
# GUI events
Irssi::signal_add_last('window changed', 'window_change');
Irssi::signal_add_last('window destroyed', 'window_close');
Irssi::signal_add_last('window created', 'window_open');
Irssi::signal_add_last('gui key pressed', 'key_pressed');
# Statusbar
Irssi::statusbar_item_register('typing_notice', undef, 'typing_notice');
Irssi::statusbars_recreate_items();