dotfiles/dot_irssi/scripts/nm.pl

694 lines
18 KiB
Perl

use Irssi;
use strict;
use vars qw($VERSION %IRSSI);
$VERSION="0.3.10";
%IRSSI = (
authors=> 'BC-bd',
contact=> 'bd@bc-bd.org',
name=> 'nm',
description=> 'right aligned nicks depending on longest nick',
license=> 'GPL v2',
url=> 'http://bc-bd.org/blog/irssi/',
);
# $Id: 9cb009e8b7e6f5ce60294334faf88715ef01413e $
# nm.pl
# for irssi 0.8.4 by bd@bc-bd.org
#
# right aligned nicks depending on longest nick
#
# inspired by neatmsg.pl from kodgehopper <kodgehopper@netscape.net
# formats taken from www.irssi.de
# thanks to adrianel <adrinael@nuclearzone.org> for some hints
# thanks to Eric Wald <eswald@gmail.com> for the left alignment patch
# inspired by nickcolor.pl by Timo Sirainen and Ian Peters
# thanks to And1 <and1@meinungsverstaerker.de> for a small patch
# thanks to berber@tzi.de for the save/load patch
# thanks to Dennis Heimbert <dennis.heimbert@gmail.com> for a bug report/patch
# thanks to Roy Sigurd Karlsbakk <roy@karlsbakk.net> for an autosave patch
#
#########
# USAGE
###
#
# use
#
# /neatcolor help
#
# for help on available commands
#
#########
# OPTIONS
#########
my $help = "
/set neat_colorize <ON|OFF>
* ON : colorize nicks
* OFF : do not colorize nicks
/set neat_colors <string>
Use these colors when colorizing nicks, eg:
/set neat_colors yYrR
See the file formats.txt on an explanation of what colors are
available.
/set neat_left_actions <ON|OFF>
* ON : print nicks left-aligned on actions
* OFF : print nicks right-aligned on actions
/set neat_left_messages <ON|OFF>
* ON : print nicks left-aligned on messages
* OFF : print nicks right-aligned on messages
/set neat_right_mode <ON|OFF>
* ON : print the mode of the nick e.g @%+ after the nick
* OFF : print it left of the nick
/set neat_maxlength <number>
* number : Maximum length of Nicks to display. Longer nicks are truncated.
* 0 : Do not truncate nicks.
/set neat_melength <number>
* number : number of spaces to substract from /me padding
/set neat_ignorechars <str>
* str : regular expression used to filter out unwanted characters in
nicks. this can be used to assign the same color for similar
nicks, e.g. foo and foo_:
/set neat_ignorechars [_]
/set neat_allow_shrinking <ON|OFF>
* ON : shrink padding when longest nick disappears
* OFF : do not shrink, only allow growing
/set neat_autosave <number>
* number : autosave after <number> seconds, defaults to 60. Set to 0 to
disable.
";
#
###
################
###
#
# Changelog
#
# Version 0.3.11
# - added autosave, idea from Roy Sigurd Karlsbakk
#
# Version 0.3.10
# - fix losing of saved color when changing nick shares more than one channel
# with you
#
# Version 0.3.9
# - fix longest nick calculation for nicks shorter than the current longest
# nick
# - updated url
#
# Version 0.3.8
# - fixed error in the nickchange tracking code, reported by Kevin Ballard
# - added --all switch to reset command
# - skip broken lines in saved_colors
#
# Version 0.3.7
# - fixed crash when calling /neatcolor without parameters
# - fixed url
#
# Version 0.3.6
# - added option to ignore certain characters from color hash building, see
# https://bc-bd.org/trac/irssi/ticket/22
# - added option to save and specify colors for nicks, see
# https://bc-bd.org/trac/irssi/ticket/23
# - added option to disallow shrinking, see
# https://bc-bd.org/trac/irssi/ticket/12
#
# Version 0.3.5
# - now also aligning own messages in queries
#
# Version 0.3.4
# - fxed off by one error in nick_to_color, patch by jrib, see
# https://bc-bd.org/trac/irssi/ticket/24
#
# Version 0.3.3
# - added support for alignment in queries, see
# https://bc-bd.org/trac/irssi/ticket/21
#
# Version 0.3.2
# - integrated left alignment patch from Eric Wald <eswald@gmail.com>, see
# https://bc-bd.org/trac/irssi/ticket/18
#
# Version 0.3.1
# - /me padding, see https://bc-bd.org/trac/irssi/ticket/17
#
# Version 0.3.0
# - integrate nick coloring support
#
# Version 0.2.1
# - moved neat_maxlength check to reformat() (thx to Jerome De Greef <jdegreef@brutele.be>)
#
# Version 0.2.0
# - by adrianel <adrinael@nuclearzone.org>
# * reformat after setup reload
# * maximum length of nicks
#
# Version 0.1.0
# - got lost somewhere
#
# Version 0.0.2
# - ugly typo fixed
#
# Version 0.0.1
# - initial release
#
###
################
###
#
# BUGS
#
# Empty nicks, eg "<> message"
# This seems to be triggered by some themes. As of now there is no known
# fix other than changing themes, see
# https://bc-bd.org/trac/irssi/ticket/19
#
# Well, it's a feature: due to the lacking support of extendable themes
# from irssi it is not possible to just change some formats per window.
# This means that right now all windows are aligned with the same nick
# length, which can be somewhat annoying.
# If irssi supports extendable themes, I will include per-server indenting
# and a setting where you can specify servers you don't want to be indented
#
###
################
my ($longestNick, %saved_colors, @colors, $alignment, $sign, %commands,);
my ($pending_save);
my $colorize = -1;
sub reformat() {
my $max = Irssi::settings_get_int('neat_maxlength');
my $actsign = Irssi::settings_get_bool('neat_left_actions')? '': '-';
$sign = Irssi::settings_get_bool('neat_left_messages')? '': '-';
if ($max && $max < $longestNick) {
$longestNick = $max;
}
my $me = $longestNick - Irssi::settings_get_int('neat_melength');
$me = 0 if ($me < 0);
Irssi::command('^format own_action {ownaction $['.$actsign.$me.']0} $1');
Irssi::command('^format action_public {pubaction $['.$actsign.$me.']0}$1');
Irssi::command('^format action_private {pvtaction $['.$actsign.$me.']0}$1');
Irssi::command('^format action_private_query {pvtaction_query $['.$actsign.$me.']0} $2');
my $length = $sign . $longestNick;
if (Irssi::settings_get_bool('neat_right_mode') == 0) {
Irssi::command('^format own_msg {ownmsgnick $2 {ownnick $['.$length.']0}}$1');
Irssi::command('^format own_msg_channel {ownmsgnick $3 {ownnick $['.$length.']0}{msgchannel $1}}$2');
Irssi::command('^format pubmsg_me {pubmsgmenick $2 {menick $['.$length.']0}}$1');
Irssi::command('^format pubmsg_me_channel {pubmsgmenick $3 {menick $['.$length.']0}{msgchannel $1}}$2');
Irssi::command('^format pubmsg_hilight {pubmsghinick $0 $3 $['.$length.']1%n}$2');
Irssi::command('^format pubmsg_hilight_channel {pubmsghinick $0 $4 $['.$length.']1{msgchannel $2}}$3');
Irssi::command('^format pubmsg {pubmsgnick $2 {pubnick $['.$length.']0}}$1');
Irssi::command('^format pubmsg_channel {pubmsgnick $2 {pubnick $['.$length.']0}}$1');
} else {
Irssi::command('^format own_msg {ownmsgnick {ownnick $['.$length.']0$2}}$1');
Irssi::command('^format own_msg_channel {ownmsgnick {ownnick $['.$length.']0$3}{msgchannel $1}}$2');
Irssi::command('^format pubmsg_me {pubmsgmenick {menick $['.$length.']0}$2}$1');
Irssi::command('^format pubmsg_me_channel {pubmsgmenick {menick $['.$length.']0$3}{msgchannel $1}}$2');
Irssi::command('^format pubmsg_hilight {pubmsghinick $0 $0 $['.$length.']1$3%n}$2');
Irssi::command('^format pubmsg_hilight_channel {pubmsghinick $0 $['.$length.']1$4{msgchannel $2}}$3');
Irssi::command('^format pubmsg {pubmsgnick {pubnick $['.$length.']0$2}}$1');
Irssi::command('^format pubmsg_channel {pubmsgnick {pubnick $['.$length.']0$2}}$1');
}
# format queries
Irssi::command('^format own_msg_private_query {ownprivmsgnick {ownprivnick $['.$length.']2}}$1');
Irssi::command('^format msg_private_query {privmsgnick $['.$length.']0}$2');
};
sub findLongestNick {
$longestNick = 0;
# get own nick length
map {
my $len = length($_->{nick});
$longestNick = $len if ($len > $longestNick);
} Irssi::servers();
# find longest other nick
foreach (Irssi::channels()) {
foreach ($_->nicks()) {
my $len = length($_->{nick});
$longestNick = $len if ($len > $longestNick);
}
}
reformat();
}
sub delayed_save
{
# skip if we have already a save pending. we don't reset the timeout
# here, else you could end up with changes never being automatically
# saved if they happen more often than <neat_autosave> seconds
return if $pending_save;
return unless Irssi::settings_get_int('neat_autosave');
Irssi::timeout_add_once(Irssi::settings_get_int('neat_autosave') * 1000,
\&save_colors, undef);
}
# a new nick was created
sub sig_newNick
{
my ($channel, $nick) = @_;
my $len = length($nick->{nick});
if ($len > $longestNick) {
$longestNick = $len;
reformat();
}
return if (exists($saved_colors{$nick->{nick}}));
$saved_colors{$nick->{nick}} = "%".nick_to_color($nick->{nick});
delayed_save();
}
# something changed
sub sig_changeNick
{
my ($channel, $nick, $old_nick) = @_;
# if no saved color exists, we already handled this nickchange. irssi
# generates one signal per channel the nick is in, so if you share more
# than one channel with this nick, you'd lose the coloring.
return unless exists($saved_colors{$old_nick});
# we need to update the saved colorors hash independent of nick lenght
$saved_colors{$nick->{nick}} = $saved_colors{$old_nick};
delete $saved_colors{$old_nick};
delayed_save();
my $new = length($nick->{nick});
# in case the new nick is longer than the old one, simply remember this
# as the new longest nick and reformat.
#
# if the new nick is as long as the known longest nick nothing has to be
# done
#
# if the new nick is shorter than the current longest one and if the
# user allows us to shrink, find new longest nick and reformat.
if ($new > $longestNick) {
$longestNick = $new;
} elsif ($new == $longestNick) {
return;
} else {
return unless Irssi::settings_get_bool('neat_allow_shrinking');
findLongestNick();
}
reformat();
}
sub sig_removeNick
{
my ($channel, $nick) = @_;
my $thisLen = length($nick->{nick});
# we only need to recalculate if this was the longest nick and we are
# allowed to shrink
if ($thisLen == $longestNick && Irssi::settings_get_bool('neat_allow_shrinking')) {
findLongestNick();
reformat();
}
# we do not remove a known color for a gone nick, as they may return
}
# based on simple_hash from nickcolor.pl
sub nick_to_color($) {
my ($string) = @_;
chomp $string;
my $ignore = Irssi::settings_get_str("neat_ignorechars");
$string =~ s/$ignore//g;
my $counter;
foreach my $char (split(//, $string)) {
$counter += ord $char;
}
return $colors[$counter % ($#colors + 1)];
}
sub color_left($) {
Irssi::command('^format pubmsg {pubmsgnick $2 {pubnick '.$_[0].'$['.$sign.$longestNick.']0}}$1');
Irssi::command('^format pubmsg_channel {pubmsgnick $2 {pubnick '.$_[0].'$['.$sign.$longestNick.']0}}$1');
}
sub color_right($) {
Irssi::command('^format pubmsg {pubmsgnick {pubnick '.$_[0].'$['.$sign.$longestNick.']0}$2}$1');
Irssi::command('^format pubmsg_channel {pubmsgnick {pubnick '.$_[0].'$['.$sign.$longestNick.']0}$2}$1');
}
sub sig_public {
my ($server, $msg, $nick, $address, $target) = @_;
&$alignment($saved_colors{$nick});
}
sub sig_setup {
@colors = split(//, Irssi::settings_get_str('neat_colors'));
# check left or right alignment
if (Irssi::settings_get_bool('neat_right_mode') == 0) {
$alignment = \&color_left;
} else {
$alignment = \&color_right;
}
# check if we switched coloring on or off
my $new = Irssi::settings_get_bool('neat_colorize');
if ($new != $colorize) {
if ($new) {
Irssi::signal_add('message public', 'sig_public');
} else {
if ($colorize >= 0) {
Irssi::signal_remove('message public', 'sig_public');
}
}
}
$colorize = $new;
reformat();
&$alignment('%w');
}
# make sure that every nick has an assigned color
sub assert_colors() {
foreach (Irssi::channels()) {
foreach ($_->nicks()) {
next if (exists($saved_colors{$_->{nick}}));
$saved_colors{$_->{nick}} = "%".nick_to_color($_->{nick});
delayed_save();
}
}
}
# load colors from file
sub load_colors() {
open(FID, "<", $ENV{HOME}."/.irssi/saved_colors") || return;
while (<FID>) {
chomp;
my ($k, $v) = split(/:/);
# skip broken lines, those may have been introduced by nm.pl
# version 0.3.7 and earlier
if ($k eq '' || $v eq '') {
neat_log(Irssi::active_win(), "Warning, broken line in saved_colors file, skipping '$k:$v'");
next;
}
$saved_colors{$k} = $v;
}
close(FID);
}
# save colors to file
sub save_colors() {
open(FID, ">", $ENV{HOME}."/.irssi/saved_colors");
print FID $_.":".$saved_colors{$_}."\n" foreach (keys(%saved_colors));
close(FID);
# clear possible pending save.
Irssi::timeout_remove($pending_save) if $pending_save;
$pending_save = undef;
}
# log a line to a window item
sub neat_log($@) {
my ($witem, @text) = @_;
$witem->print("nm.pl: ".$_) foreach(@text);
}
# show available colors
sub cmd_neatcolor_colors($) {
my ($witem, undef, undef) = @_;
neat_log($witem, "Available colors: ".join("", map { "%".$_.$_ } @colors));
}
# display the configured color for a nick
sub cmd_neatcolor_get() {
my ($witem, $nick, undef) = @_;
if (!exists($saved_colors{$nick})) {
neat_log($witem, "Error: no such nick '$nick'");
return;
}
neat_log($witem, "Color for ".$saved_colors{$nick}.$nick);
}
# display help
sub cmd_neatcolor_help() {
my ($witem, $cmd, undef) = @_;
if ($cmd) {
if (!exists($commands{$cmd})) {
neat_log($witem, "Error: no such command '$cmd'");
return;
}
if (!exists($commands{$cmd}{verbose})) {
neat_log($witem, "No additional help for '$cmd' available");
return;
}
neat_log($witem, ( "", "Help for ".uc($cmd), "" ) );
neat_log($witem, @{$commands{$cmd}{verbose}});
return;
}
neat_log($witem, split(/\n/, $help));
neat_log($witem, "Available options for /neatcolor");
neat_log($witem, " ".$_.": ".$commands{$_}{text}) foreach(sort(keys(%commands)));
my @verbose;
foreach (sort(keys(%commands))) {
push(@verbose, $_) if exists($commands{$_}{verbose});
}
neat_log($witem, "Verbose help available for: '".join(", ", @verbose)."'");
}
# list configured nicks
sub cmd_neatcolor_list() {
my ($witem, undef, undef) = @_;
neat_log($witem, "Configured nicks: ".join(", ", map { $saved_colors{$_}.$_ } sort(keys(%saved_colors))));
}
# reset a nick to its default color
sub cmd_neatcolor_reset() {
my ($witem, $nick, undef) = @_;
if ($nick eq '--all') {
%saved_colors = ();
assert_colors();
neat_log($witem, "Reset all colors");
return;
}
if (!exists($saved_colors{$nick})) {
neat_log($witem, "Error: no such nick '$nick'");
return;
}
$saved_colors{$nick} = "%".nick_to_color($nick);
delayed_save();
neat_log($witem, "Reset color for ".$saved_colors{$nick}.$nick);
}
# save configured colors to disk
sub cmd_neatcolor_save() {
my ($witem, undef, undef) = @_;
save_colors();
neat_log($witem, "color information saved");
}
# set a color for a nick
sub cmd_neatcolor_set() {
my ($witem, $nick, $color) = @_;
my @found = grep(/$color/, @colors);
if ($#found) {
neat_log($witem, "Error: trying to set unknown color '%$color$color%n'");
cmd_neatcolor_colors($witem);
return;
}
if ($witem->{type} ne "CHANNEL" && $witem->{type} ne "QUERY") {
neat_log($witem, "Warning: not a Channel/Query, can not check nick!");
neat_log($witem, "Remember, nicks are case sensitive to nm.pl");
} else {
my @nicks = grep(/^$nick$/i, map { $_->{nick} } ($witem->nicks()));
if ($#nicks < 0) {
neat_log($witem, "Warning: could not find nick '$nick' here");
} else {
if ($nicks[0] ne $nick) {
neat_log($witem, "Warning: using '$nicks[0]' instead of '$nick'");
$nick = $nicks[0];
}
}
}
$saved_colors{$nick} = "%".$color;
delayed_save();
neat_log($witem, "Set color for $saved_colors{$nick}$nick");
}
%commands = (
colors => {
text => "show available colors",
verbose => [
"COLORS",
"",
"displays all available colors",
"",
"You can restrict/define the list of available colors ".
"with the help of the neat_colors setting"
],
func => \&cmd_neatcolor_colors,
},
get => {
text => "retrieve color for a nick",
verbose => [
"GET <nick>",
"",
"displays color used for <nick>"
],
func => \&cmd_neatcolor_get,
},
help => {
text => "print this help message",
func => \&cmd_neatcolor_help,
},
list => {
text => "list configured nick/color pairs",
func => \&cmd_neatcolor_list,
},
reset => {
text => "reset color to default",
verbose => [
"RESET --all|<nick>",
"",
"resets the color used for all nicks or for <nick> to ",
"its internal default",
],
func => \&cmd_neatcolor_reset,
},
save => {
text => "save color information to disk",
verbose => [
"SAVE",
"",
"saves color information to disk, so that it survives ".
"an irssi restart.",
"",
"Color information will be automatically saved on /quit",
],
func => \&cmd_neatcolor_save,
},
set => {
text => "set a specific color for a nick",
verbose => [
"SET <nick> <color>",
"",
"use <color> for <nick>",
"",
"This command will perform a couple of sanity checks, ".
"when called from a CHANNEL/QUERY window",
"",
"EXAMPLE:",
" /neatcolor set bc-bd r",
"",
"use /neatcolor COLORS to see available colors"
],
func => \&cmd_neatcolor_set,
},
);
# the main command callback that gets called for all neatcolor commands
sub cmd_neatcolor() {
my ($data, $server, $witem) = @_;
my ($cmd, $nick, $color) = split (/ /, $data);
$cmd = lc($cmd);
# make sure we have a valid witem to print text to
$witem = Irssi::active_win() unless ($witem);
if (!exists($commands{$cmd})) {
neat_log($witem, "Error: unknown command '$cmd'");
&{$commands{"help"}{"func"}}($witem) if (exists($commands{"help"}));
return;
}
&{$commands{$cmd}{"func"}}($witem, $nick, $color);
}
Irssi::settings_add_bool('misc', 'neat_left_messages', 0);
Irssi::settings_add_bool('misc', 'neat_left_actions', 0);
Irssi::settings_add_bool('misc', 'neat_right_mode', 1);
Irssi::settings_add_int('misc', 'neat_maxlength', 0);
Irssi::settings_add_int('misc', 'neat_melength', 2);
Irssi::settings_add_bool('misc', 'neat_colorize', 1);
Irssi::settings_add_str('misc', 'neat_colors', 'rRgGyYbBmMcC');
Irssi::settings_add_str('misc', 'neat_ignorechars', '');
Irssi::settings_add_bool('misc', 'neat_allow_shrinking', 1);
Irssi::settings_add_int('misc', 'neat_autosave', 60);
Irssi::command_bind('neatcolor', 'cmd_neatcolor');
Irssi::signal_add('nicklist new', 'sig_newNick');
Irssi::signal_add('nicklist changed', 'sig_changeNick');
Irssi::signal_add('nicklist remove', 'sig_removeNick');
Irssi::signal_add('setup changed', 'sig_setup');
Irssi::signal_add_last('setup reread', 'sig_setup');
findLongestNick();
sig_setup;
load_colors();
assert_colors();
# we need to add this signal _after_ the colors have been loaded, to make sure
# no race condition exists wrt color saving
Irssi::signal_add('gui exit', 'save_colors');