renames, add irssi scripts
authorStefan Huber <s.huber@bct-electronic.com>
Tue, 6 Nov 2012 13:12:00 +0000 (14:12 +0100)
committerStefan Huber <s.huber@bct-electronic.com>
Tue, 6 Nov 2012 13:12:00 +0000 (14:12 +0100)
31 files changed:
dotfiles/conky/showmounts.sh [moved from conky/showmounts.sh with 100% similarity]
dotfiles/irssi/config [new file with mode: 0644]
dotfiles/irssi/config-sthu [new file with mode: 0644]
dotfiles/irssi/default.theme [new file with mode: 0644]
dotfiles/irssi/envy.theme [new file with mode: 0644]
dotfiles/irssi/scripts/adv_windowlist.pl [new file with mode: 0644]
dotfiles/irssi/scripts/autorun/adv_windowlist.pl [new symlink]
dotfiles/irssi/scripts/autorun/hilightwin.pl [new symlink]
dotfiles/irssi/scripts/autorun/niq.pl [new symlink]
dotfiles/irssi/scripts/autorun/nm.pl [new symlink]
dotfiles/irssi/scripts/autorun/postpone.pl [new symlink]
dotfiles/irssi/scripts/autorun/screen_away.pl [new symlink]
dotfiles/irssi/scripts/autorun/topic-diff.pl [new symlink]
dotfiles/irssi/scripts/autorun/trackbar.pl [new symlink]
dotfiles/irssi/scripts/autorun/usercount.pl [new symlink]
dotfiles/irssi/scripts/autorun/xchatnickcolor.pl [new symlink]
dotfiles/irssi/scripts/beep_beep.pl [new file with mode: 0644]
dotfiles/irssi/scripts/cap_sasl.pl [new file with mode: 0644]
dotfiles/irssi/scripts/hilightwin.pl [new file with mode: 0644]
dotfiles/irssi/scripts/nicklist.pl [new file with mode: 0644]
dotfiles/irssi/scripts/niq.pl [new file with mode: 0644]
dotfiles/irssi/scripts/nm.pl [new file with mode: 0644]
dotfiles/irssi/scripts/notify.pl [new file with mode: 0644]
dotfiles/irssi/scripts/postpone.pl [new file with mode: 0644]
dotfiles/irssi/scripts/screen_away.pl [new file with mode: 0644]
dotfiles/irssi/scripts/scriptassist.pl [new file with mode: 0644]
dotfiles/irssi/scripts/topic-diff.pl [new file with mode: 0644]
dotfiles/irssi/scripts/trackbar.pl [new file with mode: 0644]
dotfiles/irssi/scripts/usercount.pl [new file with mode: 0644]
dotfiles/irssi/scripts/xchatnickcolor.pl [new file with mode: 0644]
dotfiles/kde-autostart/displayrootwindow.sh [moved from kde-autostart/displayrootwindow.sh with 100% similarity]

diff --git a/dotfiles/irssi/config b/dotfiles/irssi/config
new file mode 100644 (file)
index 0000000..0ff6c4a
--- /dev/null
@@ -0,0 +1,313 @@
+servers = (
+  { address = "irc.stealth.net"; chatnet = "IRCnet"; port = "6668"; },
+  { address = "irc.efnet.org"; chatnet = "EFNet"; port = "6667"; },
+  { 
+    address = "irc.undernet.org";
+    chatnet = "Undernet";
+    port = "6667";
+  },
+  { address = "irc.dal.net"; chatnet = "DALnet"; port = "6667"; },
+  { 
+    address = "irc.quakenet.org";
+    chatnet = "QuakeNet";
+    port = "6667";
+  },
+  { address = "silc.silcnet.org"; chatnet = "SILC"; port = "706"; },
+  {
+    address = "irc.freenode.net";
+    chatnet = "freenode";
+    port = "7000";
+    use_ssl = "yes";
+    ssl_verify = "yes";
+    ssl_capath = "/etc/ssl/certs";
+    family = "inet";
+    autoconnect = "yes";
+  },
+  {
+    address = "irc.oftc.net";
+    chatnet = "OFTC";
+    port = "6697";
+    use_ssl = "yes";
+    ssl_verify = "yes";
+    ssl_capath = "/etc/ssl/certs";
+    family = "inet";
+    autoconnect = "no";
+  }
+);
+
+chatnets = {
+  IRCnet = {
+    type = "IRC";
+    max_kicks = "4";
+    max_msgs = "5";
+    max_whois = "4";
+    max_query_chans = "5";
+  };
+  EFNet = { 
+    type = "IRC";
+    max_kicks = "4";
+    max_msgs = "3";
+    max_whois = "1";
+  };
+  Undernet = {
+    type = "IRC";
+    max_kicks = "1";
+    max_msgs = "3";
+    max_whois = "30";
+  };
+  DALnet = {
+    type = "IRC";
+    max_kicks = "4";
+    max_msgs = "3";
+    max_whois = "30";
+  };
+  QuakeNet = {
+    type = "IRC";
+    max_kicks = "1";
+    max_msgs = "3";
+    max_whois = "30";
+  };
+  SILC = { type = "SILC"; };
+  freenode = {
+    type = "IRC";
+    #autosendcmd = "/msg nickserv identify PASSWORD; /window goto nickserv; /wait 2000; /wc";
+  };
+  OFTC = { type = "IRC"; };
+};
+
+channels = (
+  { name = "#myfavchannel"; chatnet = "freenode"; autojoin = "yes"; }
+);
+
+aliases = {
+  J = "join";
+  WJOIN = "join -window";
+  WQUERY = "query -window";
+  LEAVE = "part";
+  BYE = "quit";
+  EXIT = "quit";
+  SIGNOFF = "quit";
+  DESCRIBE = "action";
+  DATE = "time";
+  HOST = "userhost";
+  LAST = "lastlog";
+  SAY = "msg *";
+  WI = "whois";
+  WII = "whois $0 $0";
+  WW = "whowas";
+  W = "who";
+  N = "names";
+  M = "msg";
+  T = "topic";
+  C = "clear";
+  CL = "clear";
+  K = "kick";
+  KB = "kickban";
+  KN = "knockout";
+  BANS = "ban";
+  B = "ban";
+  MUB = "unban *";
+  UB = "unban";
+  IG = "ignore";
+  UNIG = "unignore";
+  SB = "scrollback";
+  UMODE = "mode $N";
+  WC = "window close";
+  WN = "window new hide";
+  SV = "say Irssi $J ($V) - http://irssi.org/";
+  GOTO = "sb goto";
+  CHAT = "dcc chat";
+  RUN = "SCRIPT LOAD";
+  CALC = "exec - if which bc &>/dev/null\\; then echo '$*' | bc | awk '{print \"$*=\"$$1}'\\; else echo bc was not found\\; fi";
+  SBAR = "STATUSBAR";
+  INVITELIST = "mode $C +I";
+  Q = "QUERY";
+  "MANUAL-WINDOWS" = "set use_status_window off;set autocreate_windows off;set autocreate_query_level none;set autoclose_windows off;set reuse_unused_windows on;save";
+  EXEMPTLIST = "mode $C +e";
+  ATAG = "WINDOW SERVER";
+};
+
+statusbar = {
+  # formats:
+  # when using {templates}, the template is shown only if it's argument isn't
+  # empty unless no argument is given. for example {sb} is printed always,
+  # but {sb $T} is printed only if $T isn't empty.
+
+  items = {
+    # start/end text in statusbars
+    barstart = "{sbstart}";
+    barend = "{sbend}";
+
+    topicbarstart = "{topicsbstart}";
+    topicbarend = "{topicsbend}";
+
+    # treated "normally", you could change the time/user name to whatever
+    time = "{sb $Z}";
+    user = "{sb {sbnickmode $cumode}$N{sbmode $usermode}{sbaway $A}}";
+
+    # treated specially .. window is printed with non-empty windows,
+    # window_empty is printed with empty windows
+    window = "{sb $winref:$tag/$itemname{sbmode $M}}";
+    window_empty = "{sb $winref{sbservertag $tag}}";
+    prompt = "{prompt $[.15]itemname}";
+    prompt_empty = "{prompt $winname}";
+    topic = " $topic";
+    topic_empty = " Irssi v$J - http://www.irssi.org";
+
+    # all of these treated specially, they're only displayed when needed
+    lag = "{sb Lag: $0-}";
+    act = "{sb Act: $0-}";
+    more = "-- more --";
+  };
+
+  # there's two type of statusbars. root statusbars are either at the top
+  # of the screen or at the bottom of the screen. window statusbars are at
+  # the top/bottom of each split window in screen.
+  default = {
+    # the "default statusbar" to be displayed at the bottom of the window.
+    # contains all the normal items.
+    window = {
+      disabled = "no";
+
+      # window, root
+      type = "window";
+      # top, bottom
+      placement = "bottom";
+      # number
+      position = "1";
+      # active, inactive, always
+      visible = "active";
+
+      # list of items in statusbar in the display order
+      items = {
+        barstart = { priority = "100"; };
+        time = { };
+        user = { };
+        window = { };
+        window_empty = { };
+        lag = { priority = "-1"; };
+        act = { priority = "10"; };
+        more = { priority = "-1"; alignment = "right"; };
+        barend = { priority = "100"; alignment = "right"; };
+        usercount = { };
+      };
+    };
+
+    # statusbar to use in inactive split windows
+    window_inact = {
+      type = "window";
+      placement = "bottom";
+      position = "1";
+      visible = "inactive";
+      items = {
+        barstart = { priority = "100"; };
+        window = { };
+        window_empty = { };
+        more = { priority = "-1"; alignment = "right"; };
+        barend = { priority = "100"; alignment = "right"; };
+      };
+    };
+
+    # we treat input line as yet another statusbar :) It's possible to
+    # add other items before or after the input line item.
+    prompt = {
+      type = "root";
+      placement = "bottom";
+      # we want to be at the bottom always
+      position = "100";
+      visible = "always";
+      items = {
+        prompt = { priority = "-1"; };
+        prompt_empty = { priority = "-1"; };
+        # treated specially, this is the real input line.
+        input = { priority = "10"; };
+      };
+    };
+
+    # topicbar
+    topic = {
+      type = "root";
+      placement = "top";
+      position = "1";
+      visible = "always";
+      items = {
+        topicbarstart = { priority = "100"; };
+        topic = { };
+        topic_empty = { };
+        topicbarend = { priority = "100"; alignment = "right"; };
+      };
+    };
+    awl_0 = {
+      items = {
+        barstart = { priority = "100"; };
+        awl_0 = { };
+        barend = { priority = "100"; alignment = "right"; };
+      };
+    };
+  };
+};
+settings = {
+  core = {
+    real_name = "Firsname Lastname";
+    user_name = "XXX";
+    nick = "XXX";
+    log_timestamp = "%F %H:%M ";
+    resolve_prefer_ipv6 = "yes";
+    timestamp_format = "%R";
+  };
+  "fe-text" = { actlist_sort = "refnum"; };
+  "fe-common/core" = {
+    beep_when_away = "yes";
+    theme = "envy";
+    beep_msg_level = "MSGS DCC DCCMSGS HILIGHT";
+    bell_beeps = "yes";
+    beep_when_window_active = "yes";
+    autolog = "yes";
+    autolog_path = "~/.irssi/logs/$tag/$0.%Y-%m.log";
+    term_charset = "UTF-8";
+    show_names_on_join = "yes";
+    show_nickmode = "yes";
+    use_status_window = "yes";
+    use_msgs_window = "no";
+    activity_hide_level = "JOINS QUITS PARTS NICKS MODES";
+    emphasis_multiword = "yes";
+    hilight_level = "PUBLIC DCCMSGS";
+  };
+  "perl/core/scripts" = {
+    nicklist_width = "10";
+    nicklist_height = "20";
+    nicklist_automode = "screen";
+    screen_away_active = "yes";
+    screen_away_nick = "";
+    #beep_cmd = "/usr/bin/ogg123 --quiet /usr/share/sounds/KDE-Im-Irc-Event.ogg &";
+    neat_maxlength = "12";
+    screen_away_message = "Currently away";
+  };
+};
+hilights = ( { text = "NICK XXX"; nick = "yes"; word = "yes"; } );
+logs = { };
+windows = {
+  1 = {
+    immortal = "yes";
+    name = "(status)";
+    level = "ALL";
+    sticky = "yes";
+    parent = "3";
+  };
+  2 = { name = "hilight"; sticky = "yes"; };
+  3 = {
+    items = (
+      {
+        type = "CHANNEL";
+        chat_type = "IRC";
+        name = "#myfavchannel";
+        tag = "freenode";
+      }
+    );
+    sticky = "yes";
+  };
+};
+mainwindows = {
+  3 = { first_line = "8"; lines = "61"; };
+  2 = { first_line = "1"; lines = "7"; };
+};
diff --git a/dotfiles/irssi/config-sthu b/dotfiles/irssi/config-sthu
new file mode 100644 (file)
index 0000000..a58955e
--- /dev/null
@@ -0,0 +1,380 @@
+servers = (
+  { address = "irc.stealth.net"; chatnet = "IRCnet"; port = "6668"; },
+  { address = "irc.efnet.org"; chatnet = "EFNet"; port = "6667"; },
+  { 
+    address = "irc.undernet.org";
+    chatnet = "Undernet";
+    port = "6667";
+  },
+  { address = "irc.dal.net"; chatnet = "DALnet"; port = "6667"; },
+  { 
+    address = "irc.quakenet.org";
+    chatnet = "QuakeNet";
+    port = "6667";
+  },
+  { address = "silc.silcnet.org"; chatnet = "SILC"; port = "706"; },
+  {
+    address = "irc.freenode.net";
+    chatnet = "freenode";
+    port = "7000";
+    use_ssl = "yes";
+    ssl_verify = "yes";
+    ssl_capath = "/etc/ssl/certs";
+    family = "inet";
+    autoconnect = "yes";
+  },
+  {
+    address = "irc.oftc.net";
+    chatnet = "OFTC";
+    port = "6697";
+    use_ssl = "yes";
+    ssl_verify = "yes";
+    ssl_capath = "/etc/ssl/certs";
+    family = "inet";
+    autoconnect = "yes";
+  },
+  {
+    address = "testing.bitlbee.org";
+    chatnet = "bitlbee";
+    port = "6668";
+    use_ssl = "yes";
+    ssl_verify = "no";
+    family = "inet";
+    autoconnect = "yes";
+  }
+);
+
+chatnets = {
+  IRCnet = {
+    type = "IRC";
+    max_kicks = "4";
+    max_msgs = "5";
+    max_whois = "4";
+    max_query_chans = "5";
+  };
+  EFNet = { 
+    type = "IRC";
+    max_kicks = "4";
+    max_msgs = "3";
+    max_whois = "1";
+  };
+  Undernet = {
+    type = "IRC";
+    max_kicks = "1";
+    max_msgs = "3";
+    max_whois = "30";
+  };
+  DALnet = {
+    type = "IRC";
+    max_kicks = "4";
+    max_msgs = "3";
+    max_whois = "30";
+  };
+  QuakeNet = {
+    type = "IRC";
+    max_kicks = "1";
+    max_msgs = "3";
+    max_whois = "30";
+  };
+  SILC = { type = "SILC"; };
+  freenode = {
+    type = "IRC";
+    #autosendcmd = "/msg nickserv identify QPFR6YdanYOkZvYcLsLO; /window goto nickserv; /wait 2000; /wc";
+    autosendcmd = "/msg nickserv identify Naihu2udaqua5Ule; /window goto nickserv; /wait 2000; /wc";
+  };
+  OFTC = {
+    type = "IRC";
+    #autosendcmd = "/msg nickserv identify aLeno8CY4m5Z4HQWlA7iR+BpIkghGu7H6QxDWHHh; /wait 2000; /window goto nickserv; /wc";
+    autosendcmd = "/msg nickserv identify kiesah8ter5Vooth; /wait 2000; /window goto nickserv; /wc";
+  };
+  bitlbee = {
+    type = "IRC";
+    autosendcmd = "/msg nickserv identify 2ciCuvKX; /wait 2000; /window goto nickserv; /wc";
+  };
+};
+
+channels = (
+  { name = "#irssi"; chatnet = "ircnet"; autojoin = "No"; },
+  { name = "silc"; chatnet = "silc"; autojoin = "No"; },
+  { name = "#compgeo"; chatnet = "OFTC"; autojoin = "yes"; },
+  { name = "#wavelab"; chatnet = "freenode"; autojoin = "yes"; },
+  { name = "#chaossbg"; chatnet = "freenode"; autojoin = "yes"; },
+  { 
+    name = "#gentoo-anfaenger";
+    chatnet = "freenode";
+    autojoin = "No";
+  },
+  { name = "#gentoo.de"; chatnet = "freenode"; autojoin = "No"; }
+);
+
+aliases = {
+  J = "join";
+  WJOIN = "join -window";
+  WQUERY = "query -window";
+  LEAVE = "part";
+  BYE = "quit";
+  EXIT = "quit";
+  SIGNOFF = "quit";
+  DESCRIBE = "action";
+  DATE = "time";
+  HOST = "userhost";
+  LAST = "lastlog";
+  SAY = "msg *";
+  WI = "whois";
+  WII = "whois $0 $0";
+  WW = "whowas";
+  W = "who";
+  N = "names";
+  M = "msg";
+  T = "topic";
+  C = "clear";
+  CL = "clear";
+  K = "kick";
+  KB = "kickban";
+  KN = "knockout";
+  BANS = "ban";
+  B = "ban";
+  MUB = "unban *";
+  UB = "unban";
+  IG = "ignore";
+  UNIG = "unignore";
+  SB = "scrollback";
+  UMODE = "mode $N";
+  WC = "window close";
+  WN = "window new hide";
+  SV = "say Irssi $J ($V) - http://irssi.org/";
+  GOTO = "sb goto";
+  CHAT = "dcc chat";
+  RUN = "SCRIPT LOAD";
+  CALC = "exec - if which bc &>/dev/null\\; then echo '$*' | bc | awk '{print \"$*=\"$$1}'\\; else echo bc was not found\\; fi";
+  SBAR = "STATUSBAR";
+  INVITELIST = "mode $C +I";
+  Q = "QUERY";
+  "MANUAL-WINDOWS" = "set use_status_window off;set autocreate_windows off;set autocreate_query_level none;set autoclose_windows off;set reuse_unused_windows on;save";
+  EXEMPTLIST = "mode $C +e";
+  ATAG = "WINDOW SERVER";
+};
+
+statusbar = {
+  # formats:
+  # when using {templates}, the template is shown only if it's argument isn't
+  # empty unless no argument is given. for example {sb} is printed always,
+  # but {sb $T} is printed only if $T isn't empty.
+
+  items = {
+    # start/end text in statusbars
+    barstart = "{sbstart}";
+    barend = "{sbend}";
+
+    topicbarstart = "{topicsbstart}";
+    topicbarend = "{topicsbend}";
+
+    # treated "normally", you could change the time/user name to whatever
+    time = "{sb $Z}";
+    user = "{sb {sbnickmode $cumode}$N{sbmode $usermode}{sbaway $A}}";
+
+    # treated specially .. window is printed with non-empty windows,
+    # window_empty is printed with empty windows
+    window = "{sb $winref:$tag/$itemname{sbmode $M}}";
+    window_empty = "{sb $winref{sbservertag $tag}}";
+    prompt = "{prompt $[.15]itemname}";
+    prompt_empty = "{prompt $winname}";
+    topic = " $topic";
+    topic_empty = " Irssi v$J - http://www.irssi.org";
+
+    # all of these treated specially, they're only displayed when needed
+    lag = "{sb Lag: $0-}";
+    act = "{sb Act: $0-}";
+    more = "-- more --";
+  };
+
+  # there's two type of statusbars. root statusbars are either at the top
+  # of the screen or at the bottom of the screen. window statusbars are at
+  # the top/bottom of each split window in screen.
+  default = {
+    # the "default statusbar" to be displayed at the bottom of the window.
+    # contains all the normal items.
+    window = {
+      disabled = "no";
+
+      # window, root
+      type = "window";
+      # top, bottom
+      placement = "bottom";
+      # number
+      position = "1";
+      # active, inactive, always
+      visible = "active";
+
+      # list of items in statusbar in the display order
+      items = {
+        barstart = { priority = "100"; };
+        time = { };
+        user = { };
+        window = { };
+        window_empty = { };
+        lag = { priority = "-1"; };
+        act = { priority = "10"; };
+        more = { priority = "-1"; alignment = "right"; };
+        barend = { priority = "100"; alignment = "right"; };
+        usercount = { };
+      };
+    };
+
+    # statusbar to use in inactive split windows
+    window_inact = {
+      type = "window";
+      placement = "bottom";
+      position = "1";
+      visible = "inactive";
+      items = {
+        barstart = { priority = "100"; };
+        window = { };
+        window_empty = { };
+        more = { priority = "-1"; alignment = "right"; };
+        barend = { priority = "100"; alignment = "right"; };
+      };
+    };
+
+    # we treat input line as yet another statusbar :) It's possible to
+    # add other items before or after the input line item.
+    prompt = {
+      type = "root";
+      placement = "bottom";
+      # we want to be at the bottom always
+      position = "100";
+      visible = "always";
+      items = {
+        prompt = { priority = "-1"; };
+        prompt_empty = { priority = "-1"; };
+        # treated specially, this is the real input line.
+        input = { priority = "10"; };
+      };
+    };
+
+    # topicbar
+    topic = {
+      type = "root";
+      placement = "top";
+      position = "1";
+      visible = "always";
+      items = {
+        topicbarstart = { priority = "100"; };
+        topic = { };
+        topic_empty = { };
+        topicbarend = { priority = "100"; alignment = "right"; };
+      };
+    };
+    awl_0 = {
+      items = {
+        barstart = { priority = "100"; };
+        awl_0 = { };
+        barend = { priority = "100"; alignment = "right"; };
+      };
+    };
+  };
+};
+settings = {
+  core = {
+    real_name = "Stefan Huber";
+    user_name = "shuber";
+    nick = "shuber";
+    log_timestamp = "%F %H:%M ";
+    resolve_prefer_ipv6 = "yes";
+    timestamp_format = "%R";
+  };
+  "fe-text" = { actlist_sort = "refnum"; };
+  "fe-common/core" = {
+    beep_when_away = "yes";
+    theme = "envy";
+    beep_msg_level = "MSGS DCC DCCMSGS HILIGHT";
+    bell_beeps = "yes";
+    beep_when_window_active = "yes";
+    autolog = "yes";
+    autolog_path = "~/.irssi/logs/$tag/$0.%Y-%m.log";
+    term_charset = "UTF-8";
+    show_names_on_join = "yes";
+    show_nickmode = "yes";
+    use_status_window = "yes";
+    use_msgs_window = "no";
+    activity_hide_level = "JOINS QUITS PARTS NICKS MODES";
+    emphasis_multiword = "yes";
+    hilight_level = "PUBLIC DCCMSGS";
+  };
+  "perl/core/scripts" = {
+    nicklist_width = "10";
+    nicklist_height = "20";
+    nicklist_automode = "screen";
+    screen_away_active = "yes";
+    screen_away_nick = "";
+    beep_cmd = "/usr/bin/ogg123 --quiet /usr/share/sounds/KDE-Im-Irc-Event.ogg &";
+    neat_maxlength = "12";
+    screen_away_message = "Currently away";
+  };
+};
+hilights = (
+  { text = "shuber"; nick = "yes"; word = "yes"; },
+  { text = "shuber2"; nick = "yes"; word = "yes"; }
+);
+logs = { };
+windows = {
+  1 = { 
+    immortal = "yes";
+    name = "(status)";
+    level = "ALL";
+    sticky = "yes";
+  };
+  2 = { name = "hilight"; sticky = "yes"; };
+  3 = {
+    items = (
+      {
+        type = "CHANNEL";
+        chat_type = "IRC";
+        name = "&bitlbee";
+        tag = "bitlbee";
+      }
+    );
+    sticky = "yes";
+    parent = "1";
+  };
+  4 = {
+    items = (
+      {
+        type = "CHANNEL";
+        chat_type = "IRC";
+        name = "#wavelab";
+        tag = "freenode";
+      }
+    );
+    sticky = "yes";
+    parent = "1";
+  };
+  5 = {
+    items = (
+      {
+        type = "CHANNEL";
+        chat_type = "IRC";
+        name = "#compgeo";
+        tag = "OFTC";
+      }
+    );
+    sticky = "yes";
+    parent = "1";
+  };
+  6 = {
+    items = (
+      {
+        type = "CHANNEL";
+        chat_type = "IRC";
+        name = "#chaossbg";
+        tag = "freenode";
+      }
+    );
+    sticky = "yes";
+    parent = "1";
+  };
+};
+mainwindows = {
+  1 = { first_line = "4"; lines = "26"; };
+  2 = { first_line = "1"; lines = "3"; };
+};
diff --git a/dotfiles/irssi/default.theme b/dotfiles/irssi/default.theme
new file mode 100644 (file)
index 0000000..98af18b
--- /dev/null
@@ -0,0 +1,294 @@
+# When testing changes, the easiest way to reload the theme is with /RELOAD.
+# This reloads the configuration file too, so if you did any changes remember
+# to /SAVE it first. Remember also that /SAVE overwrites the theme file with
+# old data so keep backups :)
+
+# TEMPLATES:
+
+# The real text formats that irssi uses are the ones you can find with
+# /FORMAT command. Back in the old days all the colors and texts were mixed
+# up in those formats, and it was really hard to change the colors since you
+# might have had to change them in tens of different places. So, then came
+# this templating system.
+
+# Now the /FORMATs don't have any colors in them, and they also have very
+# little other styling. Most of the stuff you need to change is in this
+# theme file. If you can't change something here, you can always go back
+# to change the /FORMATs directly, they're also saved in these .theme files.
+
+# So .. the templates. They're those {blahblah} parts you see all over the
+# /FORMATs and here. Their usage is simply {name parameter1 parameter2}.
+# When irssi sees this kind of text, it goes to find "name" from abstracts
+# block below and sets "parameter1" into $0 and "parameter2" into $1 (you
+# can have more parameters of course). Templates can have subtemplates.
+# Here's a small example:
+#   /FORMAT format hello {colorify {underline world}}
+#   abstracts = { colorify = "%G$0-%n"; underline = "%U$0-%U"; }
+# When irssi expands the templates in "format", the final string would be:
+#   hello %G%Uworld%U%n
+# ie. underlined bright green "world" text.
+# and why "$0-", why not "$0"? $0 would only mean the first parameter,
+# $0- means all the parameters. With {underline hello world} you'd really
+# want to underline both of the words, not just the hello (and world would
+# actually be removed entirely).
+
+# COLORS:
+
+# You can find definitions for the color format codes in docs/formats.txt.
+
+# There's one difference here though. %n format. Normally it means the
+# default color of the terminal (white mostly), but here it means the
+# "reset color back to the one it was in higher template". For example
+# if there was /FORMAT test %g{foo}bar, and foo = "%Y$0%n", irssi would
+# print yellow "foo" (as set with %Y) but "bar" would be green, which was
+# set at the beginning before the {foo} template. If there wasn't the %g
+# at start, the normal behaviour of %n would occur. If you _really_ want
+# to use the terminal's default color, use %N.
+
+#############################################################################
+
+# default foreground color (%N) - -1 is the "default terminal color"
+default_color = "-1";
+
+# print timestamp/servertag at the end of line, not at beginning
+info_eol = "false";
+
+# these characters are automatically replaced with specified color
+# (dark grey by default)
+replaces = { "[]=" = "%K$*%n"; };
+
+abstracts = {
+  ##
+  ## generic
+  ##
+
+  # text to insert at the beginning of each non-message line
+  line_start = "%B-%W!%B-%n ";
+
+  # timestamp styling, nothing by default
+  timestamp = "$*";
+
+  # any kind of text that needs hilighting, default is to bold
+  hilight = "%_$*%_";
+
+  # any kind of error message, default is bright red
+  error = "%R$*%n";
+
+  # channel name is printed
+  channel = "%_$*%_";
+
+  # nick is printed
+  nick = "%_$*%_";
+
+  # nick host is printed
+  nickhost = "[$*]";
+
+  # server name is printed
+  server = "%_$*%_";
+
+  # some kind of comment is printed
+  comment = "[$*]";
+
+  # reason for something is printed (part, quit, kick, ..)
+  reason = "{comment $*}";
+
+  # mode change is printed ([+o nick])
+  mode = "{comment $*}";
+
+  ##
+  ## channel specific messages
+  ##
+
+  # highlighted nick/host is printed (joins)
+  channick_hilight = "%C$*%n";
+  chanhost_hilight = "{nickhost %c$*%n}";
+
+  # nick/host is printed (parts, quits, etc.)
+  channick = "%c$*%n";
+  chanhost = "{nickhost $*}";
+
+  # highlighted channel name is printed
+  channelhilight = "%c$*%n";
+
+  # ban/ban exception/invite list mask is printed
+  ban = "%c$*%n";
+
+  ##
+  ## messages
+  ##
+
+  # the basic styling of how to print message, $0 = nick mode, $1 = nick
+  msgnick = "%K<%n$0$1-%K>%n %|";
+
+  # message from you is printed. "msgownnick" specifies the styling of the
+  # nick ($0 part in msgnick) and "ownmsgnick" specifies the styling of the
+  # whole line.
+
+  # Example1: You want the message text to be green:
+  #  ownmsgnick = "{msgnick $0 $1-}%g";
+  # Example2.1: You want < and > chars to be yellow:
+  #  ownmsgnick = "%Y{msgnick $0 $1-%Y}%n";
+  #  (you'll also have to remove <> from replaces list above)
+  # Example2.2: But you still want to keep <> grey for other messages:
+  #  pubmsgnick = "%K{msgnick $0 $1-%K}%n";
+  #  pubmsgmenick = "%K{msgnick $0 $1-%K}%n";
+  #  pubmsghinick = "%K{msgnick $1 $0$2-%n%K}%n";
+  #  ownprivmsgnick = "%K{msgnick  $*%K}%n";
+  #  privmsgnick = "%K{msgnick  %R$*%K}%n";
+
+  # $0 = nick mode, $1 = nick
+  ownmsgnick = "{msgnick $0 $1-}";
+  ownnick = "%W$*%n";
+
+  # public message in channel, $0 = nick mode, $1 = nick
+  pubmsgnick = "{msgnick $0 $1-}";
+  pubnick = "%N$*%n";
+
+  # public message in channel meant for me, $0 = nick mode, $1 = nick
+  pubmsgmenick = "{msgnick $0 $1-}";
+  menick = "%Y$*%n";
+
+  # public highlighted message in channel
+  # $0 = highlight color, $1 = nick mode, $2 = nick
+  pubmsghinick = "{msgnick $1 $0$2-%n}";
+
+  # channel name is printed with message
+  msgchannel = "%K:%c$*%n";
+
+  # private message, $0 = nick, $1 = host
+  privmsg = "[%R$0%K(%r$1-%K)%n] ";
+
+  # private message from you, $0 = "msg", $1 = target nick
+  ownprivmsg = "[%r$0%K(%R$1-%K)%n] ";
+
+  # own private message in query
+  ownprivmsgnick = "{msgnick  $*}";
+  ownprivnick = "%W$*%n";
+
+  # private message in query
+  privmsgnick = "{msgnick  %R$*%n}";
+
+  ##
+  ## Actions (/ME stuff)
+  ##
+
+  # used internally by this theme
+  action_core = "%W * $*%n";
+
+  # generic one that's used by most actions
+  action = "{action_core $*} ";
+
+  # own action, both private/public
+  ownaction = "{action $*}";
+
+  # own action with target, both private/public
+  ownaction_target = "{action_core $0}%K:%c$1%n ";
+
+  # private action sent by others
+  pvtaction = "%W (*) $*%n ";
+  pvtaction_query = "{action $*}";
+
+  # public action sent by others
+  pubaction = "{action $*}";
+
+
+  ##
+  ## other IRC events
+  ##
+
+  # whois
+  whois = "%# $[8]0 : $1-";
+
+  # notices
+  ownnotice = "[%r$0%K(%R$1-%K)]%n ";
+  notice = "%K-%M$*%K-%n ";
+  pubnotice_channel = "%K:%m$*";
+  pvtnotice_host = "%K(%m$*%K)";
+  servernotice = "%g!$*%n ";
+
+  # CTCPs
+  ownctcp = "[%r$0%K(%R$1-%K)] ";
+  ctcp = "%g$*%n";
+
+  # wallops
+  wallop = "%W$*%n: ";
+  wallop_nick = "%n$*";
+  wallop_action = "%W * $*%n ";
+
+  # netsplits
+  netsplit = "%R$*%n";
+  netjoin = "%C$*%n";
+
+  # /names list
+  names_prefix = "";
+  names_nick = "[%_$0%_$1-] ";
+  names_nick_op = "{names_nick $*}";
+  names_nick_halfop = "{names_nick $*}";
+  names_nick_voice = "{names_nick $*}";
+  names_users = "[%g$*%n]";
+  names_channel = "%G$*%n";
+
+  # DCC
+  dcc = "%g$*%n";
+  dccfile = "%_$*%_";
+
+  # DCC chat, own msg/action
+  dccownmsg = "[%r$0%K($1-%K)%n] ";
+  dccownnick = "%R$*%n";
+  dccownquerynick = "%W$*%n";
+  dccownaction = "{action $*}";
+  dccownaction_target = "{action_core $0}%K:%c$1%n ";
+
+  # DCC chat, others
+  dccmsg = "[%G$1-%K(%g$0%K)%n] ";
+  dccquerynick = "%G$*%n";
+  dccaction = "%W (*dcc*) $*%n %|";
+
+  ##
+  ## statusbar
+  ##
+
+  # default background for all statusbars. You can also give
+  # the default foreground color for statusbar items.
+  sb_background = "%4%w";
+
+  # default backround for "default" statusbar group
+  #sb_default_bg = "%4";
+  # background for prompt / input line
+  sb_prompt_bg = "%n";
+  # background for info statusbar
+  sb_info_bg = "%8";
+  # background for topicbar (same default)
+  #sb_topic_bg = "%4";
+
+  # text at the beginning of statusbars. sb-item already puts
+  # space there,so we don't use anything by default.
+  sbstart = "";
+  # text at the end of statusbars. Use space so that it's never
+  # used for anything.
+  sbend = " ";
+
+  topicsbstart = "{sbstart $*}";
+  topicsbend = "{sbend $*}";
+
+  prompt = "[$*] ";
+
+  sb = " %c[%n$*%c]%n";
+  sbmode = "(%c+%n$*)";
+  sbaway = " (%GzZzZ%n)";
+  sbservertag = ":$0 (change with ^X)";
+  sbnickmode = "$0";
+
+  # activity in statusbar
+
+  # ',' separator
+  sb_act_sep = "%c$*";
+  # normal text
+  sb_act_text = "%c$*";
+  # public message
+  sb_act_msg = "%W$*";
+  # hilight
+  sb_act_hilight = "%M$*";
+  # hilight with specified color, $0 = color, $1 = text
+  sb_act_hilight_color = "$0$1-%n";
+};
diff --git a/dotfiles/irssi/envy.theme b/dotfiles/irssi/envy.theme
new file mode 100644 (file)
index 0000000..9472c70
--- /dev/null
@@ -0,0 +1,514 @@
+#      ___           ___           ___           ___
+#     /\  \         /\__\         /\__\         |\__\    
+#    /::\  \       /::|  |       /:/  /         |:|  |   
+#   /:/\:\  \     /:|:|  |      /:/  /          |:|  |   
+#  /::\~\:\  \   /:/|:|  |__   /:/__/  ___      |:|__|__ 
+# /:/\:\ \:\__\ /:/ |:| /\__\  |:|  | /\__\     /::::\__\
+# \:\~\:\ \/__/ \/__|:|/:/  /  |:|  |/:/  /    /:/~~/~   
+#  \:\ \:\__\       |:/:/  /   |:|__/:/  /    /:/  /     
+#   \:\ \/__/       |::/  /     \::::/__/     \/__/      
+#    \:\__\         /:/  /       ~~~~
+#     \/__/         \/__/                          v. 3.6
+#
+# theme by rolle (rolle @ QuakeNet, rolle_ @ Ircnet)
+# http://rollemaa.org/
+#
+# you can find the most recent version here:
+# http://rolle.tux.fi
+
+default_color = "-1";
+# Timestamp/servertag loppuun, ei alkuun
+info_eol = "false";
+replaces = { "[]=" = "$*"; };
+
+abstracts = {
+  line_start = "%K";
+  timestamp = "%K$*%n";
+  hilight = "$*";
+  error = "$*";
+  channel = "$*";
+  nick = "$*";
+  nickhost = "$*";
+  server = "$*";
+  comment = "$*";
+  reason = "{comment $*}";
+  mode = "{comment $*}";
+  channick_hilight = "$*";
+  chanhost_hilight = "{nickhost $*}";
+  channick = "$*";
+  chanhost = "{nickhost $*}";
+  channelhilight = "$*";
+  ban = "$*";
+  # Kaikissa alemmissa: $0 = mode, $1 = nick
+  msgnick = "$0$1- %|";
+  ownmsgnick = "{msgnick %K<$0 %G$1-%K>}%n";
+  ownnick = "$*";
+  pubmsgnick = "{msgnick %K<$0 %W$1-%K>}%n";
+  pubnick = "$*";
+  pubmsgmenick = "{msgnick %K<$0 %P$1-%K>}%P";
+  menick = "%P$*%n";
+  # $0 = hilightin v�ri, $1 = mode, $2 = nick
+  pubmsghinick = "{msgnick %K<$1 %P$2-%K>%P}";
+  msgchannel = ":$*";
+  # $0 = nick, $1 = hosti
+  privmsg = "[$0($1-)] ";
+  # $0 = "msg", $1 = target nick
+  ownprivmsg = "[$0($1-)] ";
+  ownprivmsgnick = "{msgnick $*}";
+  ownprivnick = "%K<%G$*%K>%n";
+  privmsgnick = "%K<%B$*%K>%n ";
+  action_core = " %r>%y>%g> %c$0-";
+  action = "{action_core $*} ";
+  ownaction = "{action $*}";
+  ownaction_target = "{action_core $*}:$1 ";
+  pvtaction = " (>>>) $* ";
+  pvtaction_query = "{action $*}";
+  pubaction = "{action $*}";
+  ownnotice = "[$0($1-)] ";
+  notice = "%g$*%K -> %G";
+  whois = "%# $[8]0 : $1-";
+  pubnotice_channel = ":$*";
+  pvtnotice_host = "($*)";
+  servernotice = "%r!$* %n";
+  ownctcp = "[$0($1-)] ";
+  ctcp = "$*";
+  wallop = "$*: ";
+  wallop_nick = "$*";
+  wallop_action = " * $* ";
+  netsplit = "$*";
+  netjoin = "%K::%g:%K $*";
+  names_prefix = "%K";
+  names_nick = "%K[$0$1-] ";
+  names_nick_op = "%K{names_nick $*}";
+  names_nick_halfop = "%K{names_nick $*}";
+  names_nick_voice = "%K{names_nick $*}";
+  names_users = "%K[$*]";
+  names_channel = "%K$*";
+  dcc = "$*";
+  dccfile = "$*";
+  dccownmsg = "[$0($1-)] ";
+  dccownnick = "$*";
+  dccownquerynick = "$*";
+  dccownaction = "{action $*}";
+  dccownaction_target = "{action_core $0}:$1 ";
+  dccmsg = "[$1-($0)] ";
+  dccquerynick = "$*";
+  dccaction = " (*dcc*) $* %|";
+  sb_background = "";
+  sb_window_bg = "%n%2";
+  sb_default_bg = "";
+  sb_prompt_bg = "%0";
+  sb_info_bg = "";
+  sb_topic_bg = "%G";
+  sbstart = "";
+  sbend = " ";
+  topicsbstart = "{sbstart $*}";
+  topicsbend = "{sbend $*}";
+  prompt = "%R!%G$*%K: ";
+  sb = " %w/%W$*%w/%n";
+  sbmode = "";
+  sbaway = "%r>%n";
+  sbservertag = "%W$0%n";
+  sbnickmode = "";
+  sb_act_sep = "%w$*%n";
+  sb_act_text = "%G$*%n";
+  sb_act_msg = "%G$*%n";
+  sb_act_hilight = "%r($*)%n";
+  sb_act_hilight_color = "%r$0$1-%n";
+};
+formats = {
+  "fe-common/core" = {
+    query_start = "%K:%K:%g:%K %gStarting query%K in {server $1} with {nick $0}";
+    join = "%K::%g:%K {channick_hilight $0} [{chanhost_hilight $1}] has %gjoined%K {channel $2}";
+    part = "%r:%K:: {channick $0} [{chanhost $1}] has %rleft%K {channel $2}";
+    quit = "%r:%K:: {channick $0} [{chanhost $1}] has %rquit%k ({reason $2})";
+    quit_once = "%r:%K:: {channick $0} [{chanhost $1}] has %rquit%K ({reason $2})";
+    nick_changed = "%K:%y:%K: {channick_hilight $0} is %ynow known as%K {channick_hilight $1}";
+    #   own_msg = "{ownmsgnick $2 {ownnick $[-10]0}}$1";
+    #   own_msg_channel = "{ownmsgnick $3 {ownnick $[-10]0}{msgchannel $1}}$2";
+    #   pubmsg_me = "{pubmsgmenick $2 {menick $[-10]0}}$1";
+    #   pubmsg_me_channel = "{pubmsgmenick $3 {menick $[-10]0}{msgchannel $1}}$2";
+    #   pubmsg_hilight = "{pubmsghinick $0 $3 $[-10]1}$2";
+    #   pubmsg_hilight_channel = "{pubmsghinick $0 $4 $[-10]1{msgchannel $2}}$3";
+    #   pubmsg = "{pubmsgnick $2 {pubnick \0030$0}}$1";
+    #   pubmsg_channel = "{pubmsgnick $3 {pubnick $[-10]0}{msgchannel $1}}$2";
+    #   line_start = "{line_start}";
+    #   line_start_irssi = "{line_start}{hilight Irssi} %W|%n ";
+    line_start_irssi = "{line_start}";
+    timestamp = "%K{timestamp $Z} ";
+    #   servertag = "$[-11]0 %W|%n ";
+    servertag = "";
+    daychange = "Day changed to %%d %%b %%Y %n";
+    talking_with = "%r:%y:%g:%K You are now talking with {nick $0}";
+    refnum_too_low = "%r:%K:: Window number must be greater than 1";
+    error_server_sticky = "%r:%K:: Window's server is %rsticky%K and it cannot be changed without -unsticky option";
+    set_server_sticky = "%K::%g:%K Window's server %gset sticky%K";
+    unset_server_sticky = "%K::%g:%K Window's server isn't sticky anymore";
+    window_name_not_unique = "%r:%K:: Window names must be unique";
+    window_level = "%K::%g:%K Window level is now $0";
+    windowlist_header = "Ref Name                 Active item     Server          Level";
+    windowlist_line = "$[3]0 %|$[20]1 $[15]2 $[15]3 $4";
+    windowlist_footer = "";
+    windows_layout_saved = "%K::%g:%K Layout of windows is now %gremembered%K next time you start irssi";
+    windows_layout_reset = "%K::%g:%K Layout of windows %greset%K to defaults";
+    window_info_header = "";
+    window_info_footer = "";
+    window_info_refnum = "Window  : {hilight #$0}";
+    window_info_refnum_sticky = "Window  : {hilight #$0 (sticky)}";
+    window_info_name = "Name    : $0";
+    window_info_history = "History : $0";
+    window_info_size = "Size    : $0x$1";
+    window_info_level = "Level   : $0";
+    window_info_server = "Server  : $0";
+    window_info_server_sticky = "Server  : $0 (sticky)";
+    window_info_theme = "Theme   : $0$1";
+    window_info_bound_items_header = "Bounds  : {hilight Name                           Server tag}";
+    window_info_bound_item = "        : $[!30]0 $[!15]1 $2";
+    window_info_bound_items_footer = "";
+    window_info_items_header = "Items   : {hilight Name                           Server tag}";
+    window_info_item = " $[7]0: $[!30]1 $2";
+    window_info_items_footer = "";
+    looking_up = "%K::%g:%K %gLooking up%K {server $0}";
+    connecting = "%K::%g:%K %gConnecting%K to {server $0} [$1] port {hilight $2}";
+    connection_established = "%K::%g:%K Connection to {server $0} %gestablished%K";
+    cant_connect = "%r:%K:: %rUnable to connect%K server {server $0} port {hilight $1} {reason $2}";
+    connection_lost = "%r:%K:: %rConnection lost%K to {server $0}";
+    lag_disconnected = "%r:%K:: %rNo PONG reply%K from server {server $0} in $1 seconds, disconnecting";
+    disconnected = "%r:%K:: %rDisconnected%K from {server $0} {reason $1}";
+    server_quit = "%r:%K:: %rDisconnecting%K from server {server $0}: {reason $1}";
+    server_changed = "%K:%y:%K: %yChanged%K to {hilight $2} server {server $1}";
+    unknown_server_tag = "%r:%K:: %rUnknown%K server tag {server $0}";
+    no_connected_servers = "%r:%K:: %rNot connected%K to any servers";
+    server_list = "{server $0}: $1:$2 ($3)";
+    server_lookup_list = "{server $0}: $1:$2 ($3) (connecting...)";
+    server_reconnect_list = "{server $0}: $1:$2 ($3) ($5 left before reconnecting)";
+    server_reconnect_removed = "%K::%g:%K %gRemoved reconnection%K to server {server $0} port {hilight $1}";
+    server_reconnect_not_found = "%r:%K:: Reconnection tag {server $0} %rnot found%K";
+    setupserver_added = "%K::%g:%K Server {server $0} %gsaved%K";
+    setupserver_removed = "%K::%g:%K Server {server $0} %gremoved%K";
+    setupserver_not_found = "%r:%K:: Server {server $0} %rnot found%K";
+    your_nick = "%r:%y:%g:%K Your nickname is {nick $0}";
+    kick = "%r:%K:: {channick $0} was %rkicked%K from {channel $1} by {nick $2} ({reason $3})";
+    invite = "%K:%y:%K: {channick_hilight $0} %yinvites%K you to {channel $1}";
+    not_invited = "You have not been invited to a channel!";
+    new_topic = "%K:%y:%K: {channick_hilight $0} %ychanged the topic%K of {channel $1} to: {hilight $2}";
+    topic_unset = "%K:%y:%K: {channick_hilight $0} %yunset the topic%K on {channel $1}";
+    your_nick_changed = "%K:%y:%K: You're %ynow known as%K {channick_hilight $1}";
+    talking_in = "%r:%y:%g:%K You are now talking in {channel $0}";
+    not_in_channels = "%r:%y:%g:%K You are not on any channels";
+    current_channel = "%r:%y:%g:%K Current channel {channel $0}";
+    names = "{names_users Users {names_channel $0}} {comment $1 total}";
+    names_prefix = "{names_prefix $0}";
+    names_nick_op = "{names_nick_op $0 $1}";
+    names_nick_halfop = "{names_nick_halfop $0 $1}";
+    names_nick_voice = "{names_nick_voice $0 $1}";
+    names_nick = "{names_nick $0 $1}";
+    endofnames = "%r:%y:%g:%K {channel $0}: Total of {hilight $1} nicks {comment {hilight $2} ops, {hilight $3} halfops, {hilight $4} voices, {hilight $5} normal}";
+    chanlist_header = "%r:%y:%g:%K You are on the following channels:";
+    chanlist_line = "{channel $[-9]0} %|+$1 ($2): $3";
+    chansetup_not_found = "Channel {channel $0} not found";
+    chansetup_added = "Channel {channel $0} saved";
+    chansetup_removed = "Channel {channel $0} removed";
+    chansetup_header = "Channel         Network    Password   Settings";
+    chansetup_line = "{channel $[15]0} %|$[10]1 $[10]2 $3";
+    chansetup_footer = "";
+    channel_move_notify = "{channel $0} is already joined in window $1, use \"/WINDOW ITEM MOVE $0\" to move it to this window";
+    #   own_msg_private = "{ownprivmsg msg $0}$1";
+    #   own_msg_private_query = "{ownprivmsgnick {ownprivnick $[-9]2}}$1";
+    #   msg_private = "{privmsg $0 $1}$2";
+    #   msg_private_query = "{privmsgnick $[-9]0}$2";
+    no_msgs_got = "%r:%y:%g:%K You have not received a message from anyone yet";
+    no_msgs_sent = "%r:%y:%g:%K You have not sent a message to anyone yet";
+    query_stop = "%r:%y:%g:%K Closing query with {nick $0}";
+    no_query = "%r:%y:%g:%K No query with {nick $0}";
+    query_server_changed = "%K:%y:%K: Query with {nick $0} %ychanged%K to server {server $1}";
+    query_move_notify = "%r:%y:%g:%K Query with {nick $0} is already created to window $1, use \"/WINDOW ITEM MOVE $0\" to move it to this window";
+    hilight_header = "%r:%y:%g:%K Highlights:";
+    hilight_line = "$[-4]0 $1 $2 $3$4$5";
+    hilight_footer = "";
+    hilight_not_found = "%r:%K:: Highlight %rnot found%K: $0";
+    hilight_removed = "%K::%g:%K Highlight %gremoved%K: $0";
+    alias_added = "%K::%g:%K Alias $0 %gadded%K";
+    alias_removed = "%K::%g:%K Alias $0 %gremoved%K";
+    alias_not_found = "%r:%K:: %rNo such%K alias: $0";
+    aliaslist_header = "%r:%y:%g:%K Aliases:";
+    aliaslist_line = "$[10]0 $1";
+    aliaslist_footer = "";
+    log_opened = "%K::%g:%K Log file {hilight $0} %gopened%K";
+    log_closed = "%K::%g:%K Log file {hilight $0} %gclosed%K";
+    log_create_failed = "%r:%K:: %rCouldn't create%K log file {hilight $0}: $1";
+    log_locked = "%r:%K:: Log file {hilight $0} is %rlocked%K, probably by another running Irssi";
+    log_not_open = "%r:%K:: Log file {hilight $0} %rnot open%K";
+    log_started = "%K::%g:%K %gStarted%K logging to file {hilight $0}";
+    log_stopped = "%r:%K:: %rStopped%K logging to file {hilight $0}";
+    log_list_header = "%r:%y:%g:%K Logs:";
+    log_list = "$0 $1: $2 $3$4";
+    log_list_footer = "";
+    windowlog_file = "%K::%g:%K Window LOGFILE set to $0";
+    windowlog_file_logging = "%r:%K:: %rCan't change%K window's logfile while log is on";
+    no_away_msgs = "%r:%K:: %rNo new%K messages in awaylog";
+    away_msgs = "%K::%g:%K {hilight $1} %gnew messages%K in awaylog:";
+    module_header = "Module               Type    Submodules";
+    module_line = "$[!20]0 $[7]1 $2";
+    module_footer = "";
+    module_already_loaded = "%r:%K:: Module {hilight $0/$1} already loaded";
+    module_not_loaded = "%r:%K:: Module {hilight $0/$1} %ris not loaded%K";
+    module_load_error = "%r:%K:: %rError%K loading module {hilight $0/$1}: $2";
+    module_invalid = "%r:%K:: {hilight $0/$1} isn't Irssi module";
+    module_loaded = "%K::%g:%K %gLoaded%K module {hilight $0/$1}";
+    module_unloaded = "%r:%K:: %rUnloaded%K module {hilight $0/$1}";
+    command_unknown = "%r:%K:: %rUnknown%K command: $0";
+    command_ambiguous = "%r:%K:: %rAmbiguous%K command: $0";
+    option_unknown = "%r:%K:: %rUnknown%K option: $0";
+    option_ambiguous = "%r:%K:: %rAmbiguous%K option: $0";
+    option_missing_arg = "%r:%K:: %rMissing%K required argument for: $0";
+    not_enough_params = "%r:%K:: %rNot enough%K parameters given";
+    not_connected = "%r:%K:: %rNot connected%K to server";
+    not_joined = "%r:%K:: %rNot joined%K to any channel";
+    chan_not_found = "%r:%K:: %rNot joined%K to such channel";
+    chan_not_synced = "%r:%K:: Channel %rnot fully synchronized%K yet, try again after a while";
+    illegal_proto = "%r:%K:: Command isn't designed for the chat protocol of the active server";
+    not_good_idea = "%r:%K:: Doing this is %rnot a good idea%K. Add -YES if you really mean it";
+    theme_saved = "%K::%g:%K Theme %gsaved%K to $0";
+    theme_save_failed = "%r:%K:: %rError%K saving theme to $0: $1";
+    theme_not_found = "%r:%K:: Theme {hilight $0} %rnot found%K";
+    theme_changed = "%K:%y:%K: %yUsing%K now theme {hilight $0} ($1)";
+    window_theme = "%K::%g:%K %gUsing%K theme {hilight $0} in this window";
+    window_theme_default = "%r:%K:: %rNo theme is set%K for this window";
+    window_theme_changed = "%K:%y:%K: %yUsing%K now theme {hilight $0} ($1) in this window";
+    window_theme_removed = "%K::%g:%K %gRemoved%K theme from this window";
+    format_title = "%:[{hilight $0}] - [{hilight $1}]%:";
+    format_subtitle = "[{hilight $0}]";
+    format_item = "$0 = $1";
+    ignored = "%K::%g:%K %gIgnoring%K {hilight $1} from {nick $0}";
+    unignored = "%K::%g:%K %gUnignored%K {nick $0}";
+    ignore_not_found = "%K::%g:%K {nick $0} %gis not%K being ignored";
+    ignore_no_ignores = "%r:%y:%g:%K There are no ignores";
+    ignore_header = "%r:%y:%g:%K Ignorance List:";
+    ignore_line = "$[-4]0 $1: $2 $3 $4";
+    ignore_footer = "";
+    unknown_chat_protocol = "%r:%K:: %rUnknown%K chat protocol: $0";
+    unknown_chatnet = "%r:%K:: %rUnknown%K chat network: $0 (create it with /IRCNET ADD)";
+    not_toggle = "%r:%K:: Value must be either ON, OFF or TOGGLE";
+    perl_error = "%r:%K:: Perl %rerror%K: $0";
+    bind_key = "$[!20]0 $1 $2";
+    bind_unknown_id = "%r:%K:: %rUnknown%K bind action: $0";
+    config_saved = "%K::%g:%K %gSaved%K configuration to file $0";
+    config_reloaded = "%K::%g:%K %gReloaded%K configuration";
+    config_modified = "%r:%y:%g:%K Configuration file was modified since irssi was last started - do you want to overwrite the possible changes?";
+    glib_error = "{error GLib $0} $1";
+    overwrite_config = "%r:%y:%g:%K Overwrite config (%gy%K/%rN%K)?";
+    set_title = "[{hilight $0}]";
+    set_item = "$0 = $1";
+    set_unknown = "%r:%K:: %rUnknown%K setting $0";
+    set_not_boolean = "%r:%K:: Setting {hilight $0} isn't boolean, use /SET";
+    translation_not_found = "%r:%K:: %rError%K opening translation table file $0: $1";
+    translation_file_error = "%r:%K:: %rError%K parsing translation table file $0";
+    pubmsg = "{pubmsgnick {pubnick $[-7]0$2}}$1";
+    own_msg = "{ownmsgnick {ownnick $[-7]0$2}}$1";
+    own_msg_channel = "{ownmsgnick {ownnick $[-7]0$3}{msgchannel $1}}$2";
+    own_msg_private_query = "{ownprivmsgnick {ownprivnick $[-7]2}}$1";
+    pubmsg_me = "{pubmsgmenick {menick $[-7]0}$2}$1";
+    pubmsg_me_channel = "{pubmsgmenick {menick $[-7]0$3}{msgchannel $1}}$2";
+    pubmsg_hilight = "{pubmsghinick $0 $0 $[-7]1$3%n}$2";
+    pubmsg_hilight_channel = "{pubmsghinick $0 $[-7]1$4{msgchannel $2}}$3";
+    pubmsg_channel = "{pubmsgnick {pubnick $[-7]0$2}}$1";
+    msg_private_query = "{privmsgnick $[-7]0}$2";
+  };
+  "fe-common/irc/dcc" = {
+    dcc_list_header = "{line_start_irssi}{dcc DCC connections:}";
+    dcc_list_footer = "{line_start_irssi}{dcc ];}";
+    #   own_dcc = "{dccownmsg dcc {dccownnick $1}}$2";
+    #   own_dcc_action = "{dccownaction_target $0 $1}$2";
+    #   own_dcc_action_query = "{dccownaction $0}$2";
+    #   own_dcc_ctcp = "{ownctcp ctcp $0}$1 $2";
+    #   dcc_msg = "{dccmsg dcc $0}$1";
+    #   action_dcc = "{dccaction $0}$1";
+    #   action_dcc_query = "{dccaction $0}$1";
+    #   own_dcc_query = "{ownmsgnick {dccownquerynick $0}}$2";
+    #   dcc_msg_query = "{privmsgnick $0}$1";
+    dcc_ctcp = "%K::%g:%K {dcc >>> DCC CTCP {hilight $1} %greceived%K from {hilight $0}: $2}";
+    dcc_chat = "%K::%g:%K {dcc DCC CHAT from {nick $0} [$1 port $2]}";
+    dcc_chat_channel = "%K::%g:%K {dcc DCC CHAT from {nick $0} [$1 port $2] %grequested%K in channel {channel $3}}";
+    dcc_chat_not_found = "%K::%g:%K {dcc No DCC CHAT %gconnection open%K to {nick $0}}";
+    dcc_chat_connected = "%K::%g:%K {dcc DCC CHAT connection with {nick $0} [$1 port $2] %gestablished%K}";
+    dcc_chat_disconnected = "%r:%K:: {dcc DCC %rlost chat%K to {nick $0}}";
+    dcc_send = "%K::%g:%K {dcc DCC SEND from {nick $0} [$1 port $2]: $3 [$4 bytes]}";
+    dcc_send_channel = "%K::%g:%K {dcc DCC SEND from {nick $0} [$1 port $2]: $3 [$4 bytes] %grequested%K in channel {channel $5}}";
+    dcc_send_exists = "%r:%K:: {dcc DCC %ralready sending%K file {dccfile $0} for {nick $1}}";
+    dcc_send_not_found = "%r:%K:: {dcc DCC %rnot sending%K file {dccfile $1} to {nick $0}}";
+    dcc_send_file_open_error = "%r:%K:: {dcc DCC %rcan't open%K file {dccfile $0}: $1}";
+    dcc_send_connected = "%K::%g:%K {dcc DCC %gsending%K file {dccfile $0} for {nick $1} [$2 port $3]}";
+    dcc_send_complete = "%K::%g:%K {dcc DCC %gsent%K file {dccfile $0} [{hilight $1}kB] for {nick $2} in {hilight $3} secs [{hilight $4kB/s}]}";
+    dcc_send_aborted = "%r:%K:: {dcc DCC %raborted%K sending file {dccfile $0} for {nick $1}}";
+    dcc_get_not_found = "%r:%K:: {dcc DCC no file offered by {nick $0}}";
+    dcc_get_connected = "%K::%g:%K {dcc DCC %greceiving%K file {dccfile $0} from {nick $1} [$2 port $3]}";
+    dcc_get_complete = "%K::%g:%K {dcc DCC %greceived%K file {dccfile $0} [$1kB] from {nick $2} in {hilight $3} secs [$4kB/s]}";
+    dcc_get_aborted = "%r:%K:: {dcc DCC %raborted%K receiving file {dccfile $0} from {nick $1}}";
+    dcc_unknown_ctcp = "%r:%K:: {dcc DCC unknown ctcp {hilight $0} from {nick $1} [$2]}";
+    dcc_unknown_reply = "%r:%K:: {dcc DCC unknown reply {hilight $0} from {nick $1} [$2]}";
+    dcc_unknown_type = "%r:%K:: {dcc DCC unknown type {hilight $0}}";
+    dcc_invalid_ctcp = "%r:%K:: {dcc DCC received CTCP {hilight $0} with %rinvalid%K parameters from {nick $1}}";
+    dcc_connect_error = "%r:%K:: {dcc DCC %rcan't connect%K to {hilight $0} port {hilight $1}}";
+    dcc_cant_create = "%r:%K:: {dcc DCC %rcan't create%K file {dccfile $0}}";
+    dcc_rejected = "%r:%K:: {dcc DCC $0 was %rrejected%K by {nick $1} [{hilight $2}]}";
+    dcc_request_send = "%K::%g:%K {dcc DCC $0 %grequest sent%K to {nick $1}: $2";
+    dcc_close = "{dcc DCC $0 close for {nick $1} [{hilight $2}]}";
+    dcc_lowport = "{dcc Warning: Port sent with DCC request is a lowport ({hilight $0, $1}) - this isn't normal. It is possible the address/port is faked (or maybe someone is just trying to bypass firewall)}";
+    dcc_list_line_chat = "%WChat ->%n {dcc $0 $1}";
+    dcc_list_line_file = "%WFile ->%n {dcc $0 $1 : $2k of $3k ($4%%) - $5kB/s - $6}";
+  };
+  "fe-text" = {
+    lastlog_too_long = "%r:%y:%g:%K /LASTLOG would print $0 lines. If you really want to print all these lines use -force option.";
+    lastlog_count = "{hilight Lastlog}: $0 lines";
+    lastlog_start = "{hilight Lastlog}:";
+    lastlog_end = "{hilight End of Lastlog}";
+    refnum_not_found = "%r:%K::%K Window number $0 %rnot found%K";
+    window_too_small = "%r:%K::%K %rNot enough room%K to resize this window";
+    cant_hide_last = "%r:%K::%K You %rcan't hide%K the last window";
+    cant_hide_sticky_windows = "%r:%K::%K You %rcan't hide%K sticky windows (use /WINDOW STICK OFF)";
+    cant_show_sticky_windows = "%r:%K::%K You %rcan't show%K sticky windows (use /WINDOW STICK OFF)";
+    window_not_sticky = "%r:%K::%K Window %ris not%K sticky";
+    window_set_sticky = "%K::%g:%K Window %gset%K sticky";
+    window_unset_sticky = "%K::%g:%K Window %gis not%K sticky anymore";
+    window_info_sticky = "Sticky  : $0";
+    window_scroll = "%K::%g:%K Window scroll mode is now $0";
+    window_scroll_unknown = "%r:%K:: %rUnknown%K scroll mode $0, must be ON, OFF or DEFAULT";
+  };
+  "fe-common/irc" = {
+    netsplit = "%r:%K:: %r{netsplit netsplit}%K %|{server $0} <-> {server $1} %rquits%K: $2";
+    netsplit_more = "%r:%K:: %r{netsplit netsplit}%K %|{server $0} <-> {server $1} %rquits:%K $2 (+$3 more, use /NETSPLIT to show all of them)";
+    netsplit_join = "%K::%g:%K %g{netjoin netsplit}%K - %|%gjoins:%K $0";
+    netsplit_join_more = "%%K::%g:%K %g{netjoin netsplit}%K - %|over, %gjoins:%K $0 (+$1 more)";
+    no_netsplits = "%r:%y:%g:%K There are no netsplits";
+    netsplits_header = "Nick      Channel    Server               Splitted server";
+    netsplits_line = "$[9]0 $[10]1 $[20]2 $3";
+    netsplits_footer = "";
+    ircnet_added = "%K::%g:%K Ircnet $0 %gsaved%K";
+    ircnet_removed = "%K::%g:%K Ircnet $0 %gremoved%K";
+    ircnet_not_found = "%r:%K:: Ircnet $0 %rnot found%K";
+    ircnet_header = "%r:%y:%g:%K Ircnets:";
+    ircnet_line = "$0: $1";
+    ircnet_footer = "";
+    setupserver_header = "Server               Port  Network    Settings";
+    setupserver_line = "%|$[!20]0 $[5]1 $[10]2 $3";
+    setupserver_footer = "";
+    joinerror_toomany = "%r:%K:: Join %rfails%K: {channel $0} (You have joined to too many channels)";
+    joinerror_full = "%r:%K:: Join %rfails%K: {channel $0} (Channel is full)";
+    joinerror_invite = "%r:%K:: Join %rfails%K: {channel $0} (You must be invited)";
+    joinerror_banned = "%r:%K:: Join %rfails%K: {channel $0} (You are banned)";
+    joinerror_bad_key = "%r:%K:: Join %rfails%K: {channel $0} (Wrong channel key)";
+    joinerror_bad_mask = "%r:%K:: Join %rfails%K: {channel $0} (Bad channel mask)";
+    joinerror_unavail = "%r:%K:: Join %rfails%K: {channel $0} (Channel is temporarily unavailable)";
+    joinerror_duplicate = "%r:%K:: Channel {channel $0} already exists - %rcannot create%K it";
+    channel_rejoin = "%r:%K:: Channel {channel $0} is temporarily %runavailable%K. Setting up a rejoin, to not rejoin, use /rmrejoins.";
+    inviting = "%K::%g:%K %gInviting%K {nick $0} to {channel $1}";
+    channel_created = "%r:%y:%g:%K Channel {channel $0} created %_$1%_";
+    url = "%r:%y:%g:%K Home page for {channelhilight $0}: $1";
+    topic = "%r:%y:%g:%K Topic for {channel $0}: %_$1%_";
+    no_topic = "%r:%y:%g:%K No topic set for %_$0%_";
+    topic_info = "%r:%y:%g:%K Topic set by {channick_hilight $0} {mode $1}";
+    chanmode_change = "%K:%y:%K: %ymode%K/{channel $0} [{mode $1}] by {nick $2}";
+    server_chanmode_change = "%K::%g: {netsplit ServerMode}%K/{channelhilight $0}: {mode $1} by {nick $2}";
+    channel_mode = "%K:%y:%K: %ymode%K/{channelhilight $0} [{mode $1}]";
+    bantype = "%K:%y:%K: Ban type %ychanged%K to {channel $0}";
+    no_bans = "%r:%y:%g:%K No bans in channel {channel $0}";
+    banlist = "$0 - {channel $1}: ban {ban $2}";
+    banlist_long = "$0 - {channel $1}: ban {ban $2} {comment by {nick $3}, $4 secs ago}";
+    ebanlist = "{channel $0}: ban exception {ban $1}";
+    ebanlist_long = "{channel $0}: ban exception {ban $1} {comment by {nick $2}, $3 secs ago}";
+    no_invitelist = "%r:%y:%g:%K Invite list is empty in channel {channel $0}";
+    invitelist = "{channel $0}: invite {ban $1}";
+    no_such_channel = "%r:%K::%K {channel $0}: %rNo such%K channel";
+    channel_change = "%K:%y:%K: %ymode%K/{channel $0} [{mode $1}]";
+    channel_synced = "%r:%y:%g:%K Join to {channel $0} was synced in {hilight $1} secs";
+    usermode_change = "%K:%y:%K: %ymode%K/{channel $0} [{mode $0}] by {channick_hilight $1}";
+    user_mode = "%r:%y:%g:%K Your user mode is {mode $0}";
+    away = "%K::%g:%K You have been %gmarked%K as being away";
+    unaway = "%K::%g:%K You are %gno longer marked%K as being away";
+    nick_away = "%r:%K::%K {nick $0} is %raway%K: $1";
+    no_such_nick = "%r:%K::%K {nick $0}: %rNo such%K nick/channel";
+    nick_in_use = "%r:%K::%K Nick {nick $0} is %ralready in use%K";
+    nick_unavailable = "%r:%K::%K Nick {nick $0} is temporarily %runavailable%K";
+    your_nick_owned = "%r:%K::%K Your nick is %rowned%K by {nick $3} {comment $1@$2}";
+    whois = "%K:%K:%g:%K %g%U{nick $0}%U%K ({nickhost $1@$2})%:%r:%y:%g:%K ircname: $3";
+    whowas = "%r:%y:%g:%K {nick $0} {nickhost $1@$2}%:%r:%y:%g:%K {whois ircname $3}";
+    whois_idle = "%r:%y:%g:%K Idle: %|since $1 days $2 hours $3 mins $4 secs";
+    whois_idle_signon = "%r:%y:%g:%K Idle: %|since $1 days $2 hours $3 mins $4 secs {comment Signed on: $5}";
+    whois_server = "%r:%y:%g:%K Server: %|$1 {comment $2}";
+    whois_oper = "%r:%y:%g:%K Info: %|{hilight $1}";
+    whois_registered = "%r:%y:%g:%K Info: %|has registered this nick";
+    whois_help = "%r:%y:%g:%K Info: %|available for help";
+    whois_modes = "%r:%y:%g:%K Modes: %|{mode $1}";
+    whois_realhost = "%r:%y:%g:%K Hostname: %|{hilight $1-}";
+    whois_usermode = "%r:%y:%g:%K Usermode: %|{mode $1}";
+    whois_channels = "%r:%y:%g:%K Channels: %|{channel $1}";
+    whois_away = "%r:%y:%g:%K Away: %|$1";
+    whois_special = "%r:%y:%g:%K Info: %|$1";
+    whois_extra = "%r:%y:%g:%K Info: %|$1";
+    end_of_whois = "%r:%K::%K %rEnd%K of WHOIS%K";
+    end_of_whowas = "%r:%y:%g:%K End of WHOWAS";
+    whois_not_found = "%r:%K::%K There is %rno such%K nick {channick_hilight $0}";
+    who = "{channelhilight $[-10]0} %|{nick $[!9]1} $[!3]2 $[!2]3 $4@$5 {comment {hilight $6}}";
+    end_of_who = "%r:%y:%g:%K End of /WHO list";
+    own_notice = "{ownnotice notice $0}$1";
+    #   own_action = "{nick $[-11]0}%n $1";
+    #   own_action_target = "{ownaction_target $0 $2}$1";
+    own_ctcp = "{ownctcp ctcp $0}$1 $2";
+    notice_server = "{servernotice $0}$1";
+    notice_public = "{notice $0{pubnotice_channel $1}}$2";
+    notice_private = "{notice $0{pvtnotice_host $1}}$2";
+    #   action_private = "{pvtaction $0}$2";
+    #   action_private_query = "{pvtaction_query $0}$2";
+    #   action_public = " {nick $[-11]0}%n $1";
+    #   action_public_channel = "{pubaction $0{msgchannel $1}}$2";
+    ctcp_reply = "%K::%g:%K %gCTCP%K {hilight $0} reply from {channick_hilight $1}: $2";
+    ctcp_reply_channel = "%K::%g:%K %gCTCP {hilight $0} reply%K from {channick_hilight $1} in channel {channel $3}: $2";
+    ctcp_ping_reply = "%K::%g:%K %gCTCP {hilight PING} reply%K from {channick_hilight $0}: $1.$[-3.0]2 seconds";
+    ctcp_requested = "%K::%g:%K %K{ctcp {hilight $0} {comment $1} %grequested%K {hilight $2} from {nick $3}}";
+    ctcp_requested_unknown = "";
+    online = "%r:%y:%g:%K Users online: {hilight $0}";
+    pong = "%K::%g:%K PONG %greceived%K from $0: $1";
+    wallops = "{wallop WALLOP {wallop_nick $0}} $1";
+    action_wallops = "{wallop WALLOP {wallop_action $0}} $1";
+    kill = "%r:%K:: You were %r{error killed}%K by {nick $0} {nickhost $1} {reason $2} {comment Path: $3}";
+    kill_server = "%r:%K:: You were %r{error killed}%K by {server $0} {reason $1} {comment Path: $2}";
+    error = "%r:%K:: %r{error ERROR}%K $0";
+    unknown_mode = "%r:%K:: %rUnknown%K mode character $0";
+    not_chanop = "%r:%K:: You're %rnot channel operator%K in {channel $0}";
+    silenced = "%K::%g:%K %gSilenced%K {nick $0}";
+    unsilenced = "%K::%g:%K %gUnsilenced%K {nick $0}";
+    silence_line = "{nick $0}: silence {ban $1}";
+    ask_oper_pass = "%r:%y:%g:%K Operator password:";
+    own_action = "{ownaction $[-5]0} $1";
+    action_private = "{pvtaction $[-5]0}$1";
+    action_private_query = "{pvtaction_query $[-5]0} $2";
+    action_public = "{pubaction $[-5]0}$1";
+  };
+  "fe-common/perl" = {
+    script_not_found = "%r:%K:: Script {hilight $0} %rnot found%K";
+    script_not_loaded = "%r:%K:: Script {hilight $0} %ris not%K loaded";
+    script_loaded = "%K::%g:%K %gLoaded%K script {hilight $0}";
+    script_unloaded = "%r:%K:: %rUnloaded%K script {hilight $0}";
+    no_scripts_loaded = "%r:%y:%g:%K No scripts are loaded";
+    script_list_header = "%r:%y:%g:%K Loaded scripts:";
+    script_list_line = "$[!15]0 $1";
+    script_list_footer = "";
+    script_error = "{error %r:%K:: %rError%K in script {hilight $0}:}";
+  };
+  #  "fe-common/irc/notifylist" = {
+  #    notify_join = "{nick $0} [$1@$2] [{hilight $3}] has joined to $4";
+  #    notify_part = "{nick $0} has left $4";
+  #    notify_away = "{nick $0} [$5] [$1@$2] [{hilight $3}] is now away: $4";
+  #    notify_unaway = "{nick $0} [$4] [$1@$2] [{hilight $3}] is now unaway";
+  #    notify_unidle = "{nick $0} [$5] [$1@$2] [{hilight $3}] just stopped idling";
+  #    notify_online = "On $0: {hilight $1}";
+  #    notify_offline = "Offline: $0";
+  #    notify_list = "$0: $1 $2 $3";
+  #    notify_list_empty = "The notify list is empty";
+  #  };
+  "Irssi::Script::xchatnickcolor" = {
+    pubmsg_hilight = "{pubmsghinick $0 $0 $[-7]1$3%n}$2";
+  };
+};
+
diff --git a/dotfiles/irssi/scripts/adv_windowlist.pl b/dotfiles/irssi/scripts/adv_windowlist.pl
new file mode 100644 (file)
index 0000000..780ab35
--- /dev/null
@@ -0,0 +1,2478 @@
+use strict; # use warnings;
+
+# {{{ debug
+
+#BEGIN {
+#      open STDERR, '>', '/home/ailin/wlstatwarnings';
+#};
+
+# FIXME COULD SOMEONE PLEASE TELL ME HOW TO SHUT UP
+#
+# ...
+# Variable "*" will not stay shared at (eval *) line *.
+# Variable "*" will not stay shared at (eval *) line *.
+# ...
+# Can't locate package Irssi::Nick for @Irssi::Irc::Nick::ISA at (eval *) line *.
+# ...
+#
+# THANKS
+
+# }}}
+
+# if you don't know how to operate folds, type zn
+
+# {{{ header
+
+use Irssi (); # which is the minimum required version of Irssi ?
+use Irssi::TextUI;
+
+use vars qw($VERSION %IRSSI);
+
+$VERSION = '0.6ca';
+%IRSSI = (
+       original_authors => q(BC-bd,        Veli,          Timo Sirainen, ).
+                           q(Wouter Coekaerts,    Jean-Yves Lefort), # (decadix)
+       original_contact => q(bd@bc-bd.org, veli@piipiip.net, tss@iki.fi, ).
+                           q(wouter@coekaerts.be, jylefort@brutele.be),
+       authors          => q(Nei),
+       contact          => q(Nei @ anti@conference.jabber.teamidiot.de),
+       url              =>  "http://anti.teamidiot.de/",
+       name             => q(awl),
+       description      => q(Adds a permanent advanced window list on the right or ).
+                           q(in a statusbar.),
+       description2     => q(Based on chanact.pl which was apparently based on ).
+                           q(lightbar.c and nicklist.pl with various other ideas ).
+                           q(from random scripts.),
+       license          => q(GNU GPLv2 or later),
+);
+
+# }}}
+
+# {{{ *** D O C U M E N T A T I O N ***
+
+# adapted by Nei
+
+###############
+# {{{ original comment
+# ###########
+# # Adds new powerful and customizable [Act: ...] item (chanelnames,modes,alias).
+# # Lets you give alias characters to windows so that you can select those with
+# # meta-<char>.
+# #
+# # for irssi 0.8.2 by bd@bc-bd.org
+# #
+# # inspired by chanlist.pl by 'cumol@hammerhart.de'
+# #
+# #########
+# # {{{ Contributors
+# #########
+# #
+# # veli@piipiip.net   /window_alias code
+# # qrczak@knm.org.pl  chanact_abbreviate_names
+# # qerub@home.se      Extra chanact_show_mode and chanact_chop_status
+# # }}}
+# }}}
+# 
+# {{{ FURTHER THANKS TO
+# ############
+# # buu, fxn, Somni, Khisanth, integral, tybalt89   for much support in any aspect perl
+# # and the channel in general ( #perl @ freenode ) and especially the ir_* functions
+# #
+# # Valentin 'senneth' Batz ( vb@g-23.org ) for the pointer to grep.pl, continuous support
+# #                                         and help in digging up ir_strip_codes
+# #
+# # OnetrixNET technology networks for the debian environment
+# #
+# # Monkey-Pirate.com / Spaceman Spiff for the webspace
+# #
+# }}}
+
+######
+# {{{ M A I N    P R O B L E M
+#####
+#
+# It is impossible to place the awl on a statusbar together with other items,
+# because I do not know how to calculate the size that it is going to get
+# granted, and therefore I cannot do the linebreaks properly.
+# This is what is missing to make a nice script out of awl.
+# If you have any ideas, please contact me ASAP :).
+# }}}
+######
+
+######
+# {{{ UTF-8 PROBLEM
+#####
+#
+# Please help me find a solution to this:
+# this be your statusbar, it is using up the maximum term size
+# [[1=1]#abc [2=2]#defghi]
+# 
+# now consider this example:i
+# "ascii" characters are marked with ., utf-8 characters with *
+# [[1=1]#... [2=2]#...***]
+#
+# you should think that this is how it would be displayed? WRONG!
+# [[1=1]#... [2=2]#...***   ]
+#
+# this is what Irssi does.. I believe my length calculating code to be correct,
+# however, I'd love to be proven wrong (or receive any other fix, too, of
+# course!)
+# }}}
+######
+
+#########
+# {{{ USAGE
+###
+#
+# copy the script to ~/.irssi/scripts/
+#
+# In irssi:
+#
+#              /script load awl
+#
+#
+# Hint: to get rid of the old [Act:] display
+#     /statusbar window remove act
+#
+# to get it back:
+#     /statusbar window add -after lag -priority 10 act
+# }}}
+##########
+# {{{ OPTIONS
+########
+#
+# {{{ /set awl_display_nokey <string>
+#     /set awl_display_key <string>
+#     /set awl_display_nokey_active <string>
+#     /set awl_display_key_active <string>
+#     * string : Format String for one window. The following $'s are expanded:
+#         $C : Name
+#         $N : Number of the Window
+#         $Q : meta-Keymap
+#         $H : Start highlighting
+#         $S : Stop highlighting
+#             /+++++++++++++++++++++++++++++++++,
+#            | ****  I M P O R T A N T :  ****  |
+#            |                                  |
+#            | don't forget  to use  $S  if you |
+#            | used $H before!                  |
+#            |                                  |
+#            '+++++++++++++++++++++++++++++++++/
+#       XXX NOTE ON *_active: there is a BUG somewhere in the length
+#       XXX calculation. currently it's best to NOT remove $H/$S from those
+#       XXX settings if you use it in the non-active settings.
+# }}}
+# {{{ /set awl_separator <string>
+#     * string : Charater to use between the channel entries
+#     you'll need to escape " " space and "$" like this:
+#     "/set awl_separator \ "
+#     "/set awl_separator \$"
+#     and {}% like this:
+#     "/set awl_separator %{"
+#     "/set awl_separator %}"
+#     "/set awl_separator %%"
+#     (reason being, that the separator is used inside a {format })
+# }}}
+# {{{ /set awl_prefer_name <ON|OFF>
+#     * this setting decides whether awl will use the active_name (OFF) or the
+#       window name as the name/caption in awl_display_*.
+#       That way you can rename windows using /window name myownname.
+# }}}
+# {{{ /set awl_hide_data <num>
+#     * num : hide the window if its data_level is below num
+#     set it to 0 to basically disable this feature,
+#               1 if you don't want windows without activity to be shown
+#               2 to show only those windows with channel text or hilight
+#               3 to show only windows with hilight
+# }}}
+# {{{ /set awl_maxlines <num>
+#     * num : number of lines to use for the window list (0 to disable, negative
+#       lock)
+# }}}
+# {{{ /set awl_columns <num>
+#     * num : number of columns to use in screen mode (0 for unlimited)
+# }}}
+# {{{ /set awl_block <num>
+#     * num : width of a column in screen mode (negative values = block display)
+#             /+++++++++++++++++++++++++++++++++,
+#            | ******  W A R N I N G !  ******  |
+#            |                                  |
+#            | If  your  block  display  looks  |
+#            | DISTORTED,  you need to add the  |
+#            | following  line to your  .theme  |
+#            | file under                       |
+#            |     abstracts = {             :  |
+#            |                                  |
+#            |       sb_act_none = "%n$*";      |
+#            |                                  |
+#            '+++++++++++++++++++++++++++++++++/
+#.02:08:26. < shi>  Irssi::current_theme()->get_format <.. can this be used?
+# }}}
+# {{{ /set awl_sbar_maxlength <ON|OFF>
+#     * if you enable the maxlength setting, the block width will be used as a
+#       maximum length for the non-block statusbar mode too.
+# }}}
+# {{{ /set awl_height_adjust <num>
+#     * num : how many lines to leave empty in screen mode
+# }}}
+# {{{ /set awl_sort <-data_level|-last_line|refnum>
+#     * you can change the window sort order with this variable
+#         -data_level : sort windows with hilight first
+#         -last_line  : sort windows in order of activity
+#         refnum      : sort windows by window number
+# }}}
+# {{{ /set awl_placement <top|bottom>
+#     /set awl_position <num>
+#     * these settings correspond to /statusbar because awl will create
+#       statusbars for you
+#     (see /help statusbar to learn more)
+# }}}
+# {{{ /set awl_all_disable <ON|OFF>
+#     * if you set awl_all_disable to ON, awl will also remove the
+#       last statusbar it created if it is empty.
+#       As you might guess, this only makes sense with awl_hide_data > 0 ;)
+# }}}
+# {{{ /set awl_automode <sbar|screen|emulate_lightbar>
+#     * this setting defines whether the window list is shown in statusbars or
+#       whether the screen hack is used (from nicklist.pl)
+# }}}
+# }}}
+##########
+# {{{ COMMANDS
+########
+# {{{ /awl paste <ON|OFF|TOGGLE>
+#     * enables or disables the screen hack windowlist. This is useful when you
+#       want to mark & copy text that you want to paste somewhere (hence the
+#       name). (ON means AWL disabled!)
+#       This is nicely bound to a function key for example.
+# }}}
+# {{{ /awl redraw
+#     * redraws the screen hack windowlist. There are many occasions where the
+#       screen hack windowlist can get destroyed so you can use this command to
+#       fix it.
+# }}}
+# }}}
+###
+# {{{ WISHES
+####
+#
+# if you fiddle with my mess, provide me with your fixes so I can benefit as well
+#
+# Nei =^.^= ( anti@conference.jabber.teamidiot.de )
+# }}}
+
+# }}}
+
+# {{{ modules
+
+#use Class::Classless;
+#use Term::Info;
+
+# }}}
+
+# {{{ global variables
+
+my $replaces = '[=]'; # AARGH!!! (chars that are always surrounded by weird
+                      # colour codes by Irssi)
+
+my $actString = [];   # statusbar texts
+my $currentLines = 0;
+my $resetNeeded;      # layout/screen has changed, redo everything
+my $needRemake;       # "normal" changes
+#my $callcount = 0;
+sub GLOB_QUEUE_TIMER () { 100 }
+my $globTime = undef; # timer to limit remake() calls
+
+
+my $SCREEN_MODE;
+my $DISABLE_SCREEN_TEMP;
+my $currentColumns = 0;
+my $screenResizing;
+my ($screenHeight, $screenWidth);
+my $screenansi = bless {
+       NAME => 'Screen::ANSI',
+       PARENTS => [],
+       METHODS => {
+               dcs   => sub { "\033P" },
+               st    => sub { "\033\\"},
+       }
+}, 'Class::Classless::X';
+#my $terminfo = new Term::Info 'xterm'; # xterm here, make this modular
+# {{{{{{{{{{{{{{{
+my $terminfo = bless { # xterm here, make this modular
+       NAME => 'Term::Info::xterm',
+       PARENTS => [],
+       METHODS => {
+ #     civis=\E[?25l,
+               civis => sub { "\033[?25l" },
+ #     sc=\E7,
+               sc    => sub { "\0337" },
+ #     cup=\E[%i%p1%d;%p2%dH,
+               cup   => sub { shift;shift; "\033[" . ($_[0] + 1) . ';' . ($_[1] + 1) . 'H' },
+ #     el=\E[K,
+               el    => sub { "\033[K" },
+ #     rc=\E8,
+               rc    => sub { "\0338" },
+ #     cnorm=\E[?25h,
+               cnorm => sub { "\033[?25h" },
+ #     setab=\E[4%p1%dm,
+               setab => sub { shift;shift; "\033[4" . $_[0] . 'm' },
+ #     setaf=\E[3%p1%dm,
+               setaf => sub { shift;shift; "\033[3" . $_[0] . 'm' },
+ #     bold=\E[1m,
+               bold  => sub { "\033[1m" },
+ #     blink=\E[5m,
+               blink => sub { "\033[5m" },
+ #     rev=\E[7m,
+               rev   => sub { "\033[7m" },
+ #     op=\E[39;49m,
+               op    => sub { "\033[39;49m" },
+       }
+}, 'Class::Classless::X';
+# }}}}}}}}}}}}}}}
+
+
+sub setc () {
+       $IRSSI{'name'}
+}
+sub set ($) {
+       setc . '_' . shift
+}
+
+# }}}
+
+
+# {{{ sbar mode
+
+my %statusbars;       # currently active statusbars
+
+# maybe I should just tie the array ?
+sub add_statusbar {
+       for (@_) {
+               # add subs
+               for my $l ($_) { {
+                       no strict 'refs'; # :P
+                       *{set$l} = sub { awl($l, @_) };
+               }; }
+               Irssi::command('statusbar ' . (set$_) . ' reset');
+               Irssi::command('statusbar ' . (set$_) . ' enable');
+               if (lc Irssi::settings_get_str(set 'placement') eq 'top') {
+                       Irssi::command('statusbar ' . (set$_) . ' placement top');
+               }
+               if ((my $x = int Irssi::settings_get_int(set 'position')) != 0) {
+                       Irssi::command('statusbar ' . (set$_) . ' position ' . $x);
+               }
+               Irssi::command('statusbar ' . (set$_) . ' add -priority 100 -alignment left barstart');
+               Irssi::command('statusbar ' . (set$_) . ' add ' . (set$_));
+               Irssi::command('statusbar ' . (set$_) . ' add -priority 100 -alignment right barend');
+               Irssi::command('statusbar ' . (set$_) . ' disable');
+               Irssi::statusbar_item_register(set$_, '$0', set$_);
+               $statusbars{$_} = {};
+       }
+}
+
+sub remove_statusbar {
+       for (@_) {
+               Irssi::command('statusbar ' . (set$_) . ' reset');
+               Irssi::statusbar_item_unregister(set$_); # XXX does this actually work ?
+               # DO NOT REMOVE the sub before you have unregistered it :))
+               for my $l ($_) { {
+                       no strict 'refs';
+                       undef &{set$l};
+               }; }
+               delete $statusbars{$_};
+       }
+}
+
+sub syncLines {
+       my $temp = $currentLines;
+       $currentLines = @$actString;
+       #Irssi::print("current lines: $temp new lines: $currentLines");
+       my $currMaxLines = Irssi::settings_get_int(set 'maxlines');
+       if ($currMaxLines > 0 and @$actString > $currMaxLines) {
+               $currentLines = $currMaxLines;
+       }
+       elsif ($currMaxLines < 0) {
+               $currentLines = abs($currMaxLines);
+       }
+       return if ($temp == $currentLines);
+       if ($currentLines > $temp) {
+               for ($temp .. ($currentLines - 1)) {
+                       add_statusbar($_);
+                       Irssi::command('statusbar ' . (set$_) . ' enable');
+               }
+       }
+       else {
+               for ($_ = ($temp - 1); $_ >= $currentLines; $_--) {
+                       Irssi::command('statusbar ' . (set$_) . ' disable');
+                       remove_statusbar($_);
+               }
+       }
+}
+
+# FIXME implement $get_size_only check, and user $item->{min|max-size} ??
+sub awl {
+       my ($line, $item, $get_size_only) = @_;
+
+       if ($needRemake) {
+               $needRemake = undef;
+               remake();
+       }
+
+       my $text = $actString->[$line];  # DO NOT set the actual $actString->[$line] to '' here or
+       $text = '' unless defined $text; # you'll screw up the statusbar counter ($currentLines)
+       $item->default_handler($get_size_only, $text, '', 1);
+}
+
+# remove old statusbars
+my %killBar;
+sub get_old_status {
+       my ($textDest, $cont, $cont_stripped) = @_;
+       if ($textDest->{'level'} == 524288 and $textDest->{'target'} eq ''
+                       and !defined($textDest->{'server'})
+       ) {
+               my $name = quotemeta(set '');
+               if ($cont_stripped =~ m/^$name(\d+)\s/) { $killBar{$1} = {}; }
+               Irssi::signal_stop();
+       }
+}
+sub killOldStatus {
+       %killBar = ();
+       Irssi::signal_add_first('print text' => 'get_old_status');
+       Irssi::command('statusbar');
+       Irssi::signal_remove('print text' => 'get_old_status');
+       remove_statusbar(keys %killBar);
+}
+#killOldStatus();
+
+# end sbar mode }}}
+
+
+# {{{ keymaps
+
+my %keymap;
+
+sub get_keymap {
+       my ($textDest, undef, $cont_stripped) = @_;
+       if ($textDest->{'level'} == 524288 and $textDest->{'target'} eq ''
+                       and !defined($textDest->{'server'})
+       ) {
+               if ($cont_stripped =~ m/((?:meta-)+)(.)\s+change_window (\d+)/) {
+                       my ($level, $key, $window) = ($1, $2, $3);
+                       my $numlevel = ($level =~ y/-//) - 1;
+                       $keymap{$window} = ('-' x $numlevel) . "$key";
+               }
+               Irssi::signal_stop();
+       }
+}
+
+sub update_keymap {
+       %keymap = ();
+       Irssi::signal_remove('command bind' => 'watch_keymap');
+       Irssi::signal_add_first('print text' => 'get_keymap');
+       Irssi::command('bind'); # stolen from grep
+       Irssi::signal_remove('print text' => 'get_keymap');
+       Irssi::signal_add('command bind' => 'watch_keymap');
+       Irssi::timeout_add_once(100, 'eventChanged', undef);
+}
+
+# watch keymap changes
+sub watch_keymap {
+       Irssi::timeout_add_once(1000, 'update_keymap', undef);
+}
+
+update_keymap();
+
+# end keymaps }}}
+
+# {{{ format handling
+
+# a bad way do do expansions but who cares
+sub expand {
+       my ($string, %format) = @_;
+       my ($exp, $repl);
+       $string =~ s/\$$exp/$repl/g while (($exp, $repl) = each(%format));
+       return $string;
+}
+
+my %strip_table = (
+       # fe-common::core::formats.c:format_expand_styles
+       #      delete                format_backs  format_fores bold_fores   other stuff
+       (map { $_ => '' } (split //, '04261537' .  'kbgcrmyw' . 'KBGCRMYW' . 'U9_8:|FnN>#[')),
+       #      escape
+       (map { $_ => $_ } (split //, '{}%')),
+);
+sub ir_strip_codes { # strip %codes
+       my $o = shift;
+       $o =~ s/(%(.))/exists $strip_table{$2} ? $strip_table{$2} : $1/gex;
+       $o
+}
+
+sub ir_parse_special {
+       my $o; my $i = shift;
+       #if ($_[0]) { # for the future?!?
+       #       eval {
+       #               $o = $_[0]->parse_special($i);
+       #       };
+       #       unless ($@) {
+       #               return $o;
+       #       }
+       #}
+       my $win = shift || Irssi::active_win();
+       my $server = Irssi::active_server();
+       if (ref $win and ref $win->{'active'}) {
+               $o = $win->{'active'}->parse_special($i);
+       }
+       elsif (ref $win and ref $win->{'active_server'}) {
+               $o = $win->{'active_server'}->parse_special($i);
+       }
+       elsif (ref $server) {
+               $o =  $server->parse_special($i);
+       }
+       else {
+               $o = Irssi::parse_special($i);
+       }
+       $o
+}
+sub ir_parse_special_protected {
+       my $o; my $i = shift;
+       $i =~ s/
+               ( \\. ) | # skip over escapes (maybe)
+               ( \$[^% $\]+ ) # catch special variables
+       /
+               if ($1) { $1 }
+               elsif ($2) { my $i2 = $2; ir_fe(ir_parse_special($i2, @_)) }
+               else { $& }
+       /gex;
+       $i
+}
+
+
+sub sb_ctfe { # Irssi::current_theme->format_expand wrapper
+       Irssi::current_theme->format_expand(
+               shift,
+               (
+                       Irssi::EXPAND_FLAG_IGNORE_REPLACES
+                               |
+                       ($_[0]?0:Irssi::EXPAND_FLAG_IGNORE_EMPTY)
+               )
+       )
+}
+sub sb_expand { # expand {format }s (and apply parse_special for $vars)
+       ir_parse_special(
+               sb_ctfe(shift)
+       )
+}
+sub sb_strip {
+       ir_strip_codes(
+               sb_expand(shift)
+       ); # does this get us the actual length of that s*ty bar :P ?
+}
+sub sb_length {
+       # unicode cludge, d*mn broken Irssi
+       # screw it, this will fail from broken joining anyway (and cause warnings)
+       my $term_type = 'term_type';
+       if (Irssi::version > 20040819) { # this is probably wrong, but I don't know
+                                             # when the setting name got changed
+               $term_type = 'term_charset';
+       }
+       #if (lc Irssi::settings_get_str($term_type) eq '8bit'
+       #               or Irssi::settings_get_str($term_type) =~ /^iso/i
+       #) {
+       #       length(sb_strip(shift))
+       #}
+       #else {
+       my $temp = sb_strip(shift);
+       # try to get the displayed width
+       my $length;
+       eval {
+               require Text::CharWidth;
+               $length = Text::CharWidth::mbswidth($temp);
+       };
+       unless ($@) {
+               return $length;
+       }
+       else {
+               if (lc Irssi::settings_get_str($term_type) eq 'utf-8') {
+                       # try to switch on utf8
+                       eval {
+                               no warnings;
+                               require Encode;
+                               #$temp = Encode::decode_utf8($temp); # thanks for the hint, but I have my
+                               #                                    # reasons for _utf8_on
+                               Encode::_utf8_on($temp);
+                       };
+               }
+               # there is nothing more I can do
+               length($temp)
+       }
+       #}
+}
+
+# !!! G*DD*MN Irssi is adding an additional layer of backslashitis per { } layer
+# !!! AND I still don't know what I need to escape.
+# !!! and NOONE else seems to know or care either.
+# !!! f*ck open source. I mean it.
+# XXX any Irssi::print debug statement leads to SEGFAULT - why ?
+
+# major parts of the idea by buu (#perl @ freenode)
+# thanks to fxn and Somni for debugging
+#      while ($_[0] =~ /(.)/g) {
+#              my $c = $1; # XXX sooo... goto kills $1
+#              if ($q eq '%') { goto ESC; }
+
+## <freenode:#perl:tybalt89> s/%(.)|(\{)|(\})|(\\|\$)/$1?$1:$2?($level++,$2):$3?($level>$min_level&&$level--,$3):'\\'x(2**$level-1).$4/ge;  # untested...
+sub ir_escape {
+       my $min_level = $_[1] || 0; my $level = $min_level;
+       my $o = shift;
+       $o =~ s/
+               (       %.      )       | # $1
+               (       \{      )       | # $2
+               (       \}      )       | # $3
+               (       \\      )       | # $4
+               (       \$(?=[^\\])     )       | # $5
+               (       \$      ) # $6
+       /
+               if ($1) { $1 } # %. escape
+               elsif ($2) { $level++; $2 } # { nesting start
+               elsif ($3) { if ($level > $min_level) { $level--; } $3 } # } nesting end
+               elsif ($4) { '\\'x(2**$level) } # \ needs \\escaping
+               elsif ($5) { '\\'x(2**$level-1) . '$' . '\\'x(2**$level-1) } # and $ needs even more because of "parse_special"
+               else { '\\'x(2**$level-1) . '$' } # $ needs \$ escaping
+       /gex;
+       $o
+}
+#sub ir_escape {
+#      my $min_level = $_[1] || 0; my $level = $min_level;
+#      my $o = shift;
+#      $o =~ s/
+#              (       %.      )       | # $1
+#              (       \{      )       | # $2
+#              (       \}      )       | # $3
+#              (       \\      |       \$      )       # $4
+#      /
+#              if ($1) { $1 } # %. escape
+#              elsif ($2) { $level++; $2 } # { nesting start
+#              elsif ($3) { if ($level > $min_level) { $level--; } $3 } # } nesting end
+#              else { '\\'x(2**($level-1)-1) . $4 } # \ or $ needs \\escaping
+#      /gex;
+#      $o
+#}
+
+sub ir_fe { # try to fix format stuff
+       my $x = shift;
+       # XXX why do I have to use two/four % here instead of one/two ??
+       # answer: you screwed up in ir_escape
+       $x =~ s/([%{}])/%$1/g;
+       #$x =~ s/(\\|\$|[ ])/\\$1/g; # XXX HOW CAN I HANDLE THE SPACES CORRECTLY XXX
+       $x =~ s/(\\|\$)/\\$1/g;
+       #$x =~ s/(\$(?=.))|(\$)/$1?"\\\$\\":"\\\$"/ge; # I think this should be here
+       #                                              # (logic), but it doesn't work
+       #                                              # that way :P
+       #$x =~ s/\\/\\\\/g; # that's right, escape escapes
+       $x
+}
+sub ir_ve { # escapes special vars but leave colours alone
+       my $x = shift;
+       #$x =~ s/([%{}])/%$1/g;
+       $x =~ s/(\\|\$|[ ])/\\$1/g;
+       $x
+}
+
+my %ansi_table;
+{
+       my ($i, $j, $k) = (0, 0, 0);
+       %ansi_table = (
+               # fe-common::core::formats.c:format_expand_styles
+               #      do                                              format_backs
+               (map { $_ => $terminfo->setab($i++) } (split //, '01234567' )),
+               #      do                                              format_fores
+               (map { $_ => $terminfo->setaf($j++) } (split //, 'krgybmcw' )),
+               #      do                                              bold_fores
+               (map { $_ => $terminfo->bold() .
+                            $terminfo->setaf($k++) } (split //, 'KRGYBMCW')),
+               # reset
+               #(map { $_ => $terminfo->op() } (split //, 'nN')),
+               (map { $_ => $terminfo->op() } (split //, 'n')),
+               (map { $_ => "\033[0m" } (split //, 'N')), # XXX quick and DIRTY
+               # flash/bright
+               F => $terminfo->blink(),
+               # reverse
+               8 => $terminfo->rev(),
+               # bold
+               (map { $_ => $terminfo->bold() } (split //, '9_')),
+               #      delete                other stuff
+               (map { $_ => '' } (split //, ':|>#[')),
+               #      escape
+               (map { $_ => $_ } (split //, '{}%')),
+       )
+}
+sub formats_to_ansi_basic {
+       my $o = shift;
+       $o =~ s/(%(.))/exists $ansi_table{$2} ? $ansi_table{$2} : $1/gex;
+       $o
+}
+
+sub lc1459 ($) { my $x = shift; $x =~ y/A-Z][\^/a-z}{|~/; $x }
+Irssi::settings_add_str(setc, 'banned_channels', '');
+Irssi::settings_add_bool(setc, 'banned_channels_on', 0);
+my %banned_channels = map { lc1459($_) => undef }
+split ' ', Irssi::settings_get_str('banned_channels');
+Irssi::settings_add_str(setc, 'fancy_abbrev', 'fancy');
+
+# }}}
+
+# {{{ main
+
+sub remake () {
+       #$callcount++;
+       #my $xx = $callcount; Irssi::print("starting remake [ $xx ]");
+       my ($hilight, $number, $display);
+       my $separator = '{sb_act_sep ' . Irssi::settings_get_str(set 'separator') .
+               '}';
+       my $custSort = Irssi::settings_get_str(set 'sort');
+       my $custSortDir = 1;
+       if ($custSort =~ /^[-!](.*)/) {
+               $custSortDir = -1;
+               $custSort = $1;
+       }
+
+       my @wins = 
+               sort {
+                       (
+                               ( (int($a->{$custSort}) <=> int($b->{$custSort})) * $custSortDir )
+                                       ||
+                               ($a->{'refnum'} <=> $b->{'refnum'})
+                       )
+               } Irssi::windows;
+       my $block = Irssi::settings_get_int(set 'block');
+       my $columns = $currentColumns;
+       my $oldActString = $actString if $SCREEN_MODE;
+       $actString = $SCREEN_MODE ? ['   A W L'] : [];
+       my $line = $SCREEN_MODE ? 1 : 0;
+       my $width = $SCREEN_MODE
+                       ?
+               $screenWidth - abs($block)*$columns + 1
+                       :
+               ([Irssi::windows]->[0]{'width'} - sb_length('{sb x}'));
+       my $height = $screenHeight - abs(Irssi::settings_get_int(set
+                       'height_adjust'));
+       my ($numPad, $keyPad) = (0, 0);
+       my %abbrevList;
+       if ($SCREEN_MODE or Irssi::settings_get_bool(set 'sbar_maxlength')
+                       or ($block < 0)
+       ) {
+               %abbrevList = ();
+               if (Irssi::settings_get_str('fancy_abbrev') !~ /^(no|off|head)/i) {
+                       my @nameList = map { ref $_ ? $_->get_active_name : '' } @wins;
+                       for (my $i = 0; $i < @nameList - 1; ++$i) {
+                               my ($x, $y) = ($nameList[$i], $nameList[$i + 1]);
+                               for ($x, $y) { s/^[+#!=]// }
+                               my $res = Algorithm::LCSS::LCSS($x, $y);
+                               if (defined $res) {
+                                       #Irssi::print("common pattern $x $y : $res");
+                                       #Irssi::print("found at $nameList[$i] ".index($nameList[$i],
+                                       #               $res));
+                                       $abbrevList{$nameList[$i]} = int (index($nameList[$i], $res) +
+                                               (length($res) / 2));
+                                       #Irssi::print("found at ".$nameList[$i+1]." ".index($nameList[$i+1],
+                                       #               $res));
+                                       $abbrevList{$nameList[$i+1]} = int (index($nameList[$i+1], $res) +
+                                               (length($res) / 2));
+                               }
+                       }
+               }
+               if ($SCREEN_MODE or ($block < 0)) {
+                       $numPad = length((sort { length($b) <=> length($a) } keys %keymap)[0]);
+                       $keyPad = length((sort { length($b) <=> length($a) } values %keymap)[0]);
+               }
+       }
+       if ($SCREEN_MODE) {
+               print STDERR $screenansi->dcs().
+                            $terminfo->civis().
+                                                $terminfo->sc().
+                                                $screenansi->st();
+               if (@$oldActString < 1) {
+                       print STDERR $screenansi->dcs().
+                                                        $terminfo->cup(0, $width).
+                                    $actString->[0].
+                                                        $terminfo->el().
+                                    $screenansi->st();
+               }
+       }
+       foreach my $win (@wins) {
+               unless ($SCREEN_MODE) {
+                       $actString->[$line] = '' unless defined $actString->[$line]
+                                       or Irssi::settings_get_bool(set 'all_disable');
+               }
+
+               # all stolen from chanact, what does this code do and why do we need it ?
+               !ref($win) && next;
+
+               my $name = $win->get_active_name;
+               $name = '*' if (Irssi::settings_get_bool('banned_channels_on') and exists
+                       $banned_channels{lc1459($name)});
+               $name = $win->{'name'} if $name ne '*' and $win->{'name'} ne ''
+                       and Irssi::settings_get_bool(set 'prefer_name');
+               my $active = $win->{'active'};
+               my $colour = $win->{'hilight_color'};
+               if (!defined $colour) { $colour = ''; }
+
+               if ($win->{'data_level'} < Irssi::settings_get_int(set 'hide_data')) {
+                       next; } # for Geert
+               if    ($win->{'data_level'} == 0) { $hilight = '{sb_act_none '; }
+               elsif ($win->{'data_level'} == 1) { $hilight = '{sb_act_text '; }
+               elsif ($win->{'data_level'} == 2) { $hilight = '{sb_act_msg '; }
+               elsif ($colour             ne '') { $hilight = "{sb_act_hilight_color $colour "; }
+               elsif ($win->{'data_level'} == 3) { $hilight = '{sb_act_hilight '; }
+               else                              { $hilight = '{sb_act_special '; }
+
+               $number = $win->{'refnum'};
+               my @display = ('display_nokey');
+               if (defined $keymap{$number} and $keymap{$number} ne '') {
+                       unshift @display, map { (my $cpy = $_) =~ s/_no/_/; $cpy } @display;
+               }
+               if (Irssi::active_win->{'refnum'} == $number) {
+                       unshift @display, map { my $cpy = $_; $cpy .= '_active'; $cpy } @display;
+               }
+               #Irssi::print("win $number [@display]: " . join '.', split //, join '<<', map {
+                       #               Irssi::settings_get_str(set $_) } @display);
+               $display = (grep { $_ }
+                       map { Irssi::settings_get_str(set $_) }
+                       @display)[0];
+                       #Irssi::print("win $number : " . join '.', split //, $display);
+
+               if ($SCREEN_MODE or Irssi::settings_get_bool(set 'sbar_maxlength')
+                               or ($block < 0)
+               ) {
+                       my $baseLength = sb_length(ir_escape(ir_ve(ir_parse_special_protected(sb_ctfe(
+                               '{sb_background}' . expand($display,
+                               C => ir_fe('x'),
+                               N => $number . (' 'x($numPad - length($number))),
+                               Q => ir_fe((' 'x($keyPad - length($keymap{$number}))) . $keymap{$number}),
+                               H => $hilight,
+                               S => '}{sb_background}'
+                       ), 1), $win)))) - 1;
+                       my $diff = abs($block) - (length($name) + $baseLength);
+                       if ($diff < 0) { # too long
+                               if (abs($diff) >= length($name)) { $name = '' } # forget it
+                               elsif (abs($diff) + 1 >= length($name)) { $name = substr($name,
+                                               0, 1); }
+                               else {
+                                       my $middle = exists $abbrevList{$name} ?
+                                       (($abbrevList{$name} + (2*(length($name) / 2)))/3) :
+                                               ((Irssi::settings_get_str('fancy_abbrev') =~ /^head/i) ?
+                                                               length($name) :
+                                               (length($name) / 2));
+                                       my $cut = int($middle - (abs($diff) / 2) + .55); 
+                                       $cut = 1 if $cut < 1;
+                                       $cut = length($name) - abs($diff) - 1 if $cut > (length($name) -
+                                               abs($diff) - 1);
+                                       $name = substr($name, 0, $cut) . '~' . substr($name, $cut +
+                                               abs($diff) + 1);
+                               }
+                       }
+                       elsif ($SCREEN_MODE or ($block < 0)) {
+                               $name .= (' ' x $diff);
+                       }
+               }
+
+               my $add = ir_ve(ir_parse_special_protected(sb_ctfe('{sb_background}' . expand($display,
+                       C => ir_fe($name),
+                       N => $number . (' 'x($numPad - length($number))),
+                       Q => ir_fe((' 'x($keyPad - length($keymap{$number}))) . $keymap{$number}),
+                       H => $hilight,
+                       S => '}{sb_background}'
+               ), 1), $win));
+               if ($SCREEN_MODE) {
+                       $actString->[$line] = $add;
+                       if ((!defined $oldActString->[$line]
+                                       or $oldActString->[$line] ne $actString->[$line])
+                                       and
+                               $line <= ($columns * $height)
+                       ) {
+                               print STDERR $screenansi->dcs().
+                                                                $terminfo->cup(($line-1) % $height+1, $width + (
+                                                                        abs($block) * int(($line-1) / $height))).
+                               formats_to_ansi_basic(sb_expand(ir_escape($actString->[$line]))).
+                                                               #$terminfo->el().
+                                                                $screenansi->st();
+                       }
+                       $line++;
+               }
+               else {
+                       #$temp =~ s/\{\S+?(?:\s(.*?))?\}/$1/g;
+                       #$temp =~ s/\\\\\\\\/\\/g; # XXX I'm actually guessing here, someone point me
+                       #                          # XXX to docs please
+                       $actString->[$line] = '' unless defined $actString->[$line];
+
+                       # XXX how can I check whether the content still fits in the bar? this would
+                       # XXX allow awlstatus to reside on a statusbar together with other items...
+                       if (sb_length(ir_escape($actString->[$line] . $add)) >= $width) {
+                               # XXX doesn't correctly handle utf-8 multibyte ... help !!?
+                               $actString->[$line] .= ' ' x ($width - sb_length(ir_escape(
+                                       $actString->[$line])));
+                               $line++;
+                       }
+                       $actString->[$line] .= $add . $separator;
+                       # XXX if I use these prints, output layout gets screwed up... why ?
+                       #Irssi::print("line $line: ".$actString->[$line]);
+                       #Irssi::print("temp $line: ".$temp);
+               }
+       }
+
+       if ($SCREEN_MODE) {
+               while ($line <= ($columns * $height)) {
+                       print STDERR $screenansi->dcs().
+                                                        $terminfo->cup(($line-1) % $height+1, $width + (
+                                                                abs($block) * int(($line-1) / $height))).
+                                                        $terminfo->el().
+                                                        $screenansi->st();
+                       $line++;
+               }
+               print STDERR $screenansi->dcs().
+                                                $terminfo->rc().
+                            $terminfo->cnorm().
+                                                $screenansi->st();
+       }
+       else {
+               # XXX the Irssi::print statements lead to the MOST WEIRD results
+               # e.g.: the loop gets executed TWICE for p > 0 ?!?
+               for (my $p = 0; $p < @$actString; $p++) { # wrap each line in {sb }, escape it
+                       my $x = $actString->[$p];              # properly, etc.
+                       $x =~ s/\Q$separator\E([ ]*)$/$1/;
+                       #Irssi::print("[$p]".'current:'.join'.',split//,sb_strip(ir_escape($x,0)));
+                       #Irssi::print("assumed length before:".sb_length(ir_escape($x,0)));
+                       $x = "{sb $x}";
+                       #Irssi::print("[$p]".'new:'.join'.',split//,sb_expand(ir_escape($x,0)));
+                       #Irssi::print("[$p]".'new:'.join'.',split//,ir_escape($x,0));
+                       #Irssi::print("assumed length after:".sb_length(ir_escape($x,0)));
+                       $x = ir_escape($x);
+                       #Irssi::print("[$p]".'REALnew:'.join'.',split//,sb_strip($x));
+                       $actString->[$p] = $x;
+                       # XXX any Irssi::print debug statement leads to SEGFAULT (sometimes) - why ?
+               }
+       }
+       #Irssi::print("remake [ $xx ] finished");
+}
+
+sub awlHasChanged () {
+       $globTime = undef;
+       my $temp = ($SCREEN_MODE ?
+               "\\\n" . Irssi::settings_get_int(set 'block').
+               Irssi::settings_get_int(set 'height_adjust')
+               : "!\n" . Irssi::settings_get_str(set 'placement').
+               Irssi::settings_get_int(set 'position')).
+               Irssi::settings_get_str(set 'automode');
+       if ($temp ne $resetNeeded) { wlreset(); return; }
+       #Irssi::print("awl has changed, calls to remake so far: $callcount");
+       $needRemake = 1;
+
+       #remake();
+       if (
+               ($SCREEN_MODE and !$DISABLE_SCREEN_TEMP)
+                       or
+               ($needRemake and Irssi::settings_get_bool(set 'all_disable'))
+                       or
+               (!Irssi::settings_get_bool(set 'all_disable') and $currentLines < 1)
+       ) {
+               $needRemake = undef;
+               remake();
+       }
+
+       unless ($SCREEN_MODE) {
+               # XXX Irssi crashes if I try to do this without timer, why ? What's the minimum
+               # XXX delay I need to use in the timer ?
+               Irssi::timeout_add_once(100, 'syncLines', undef);
+
+               for (keys %statusbars) {
+                       Irssi::statusbar_items_redraw(set$_);
+               }
+       }
+       else {
+               Irssi::timeout_add_once(100, 'syncColumns', undef);
+       }
+}
+
+sub eventChanged () { # Implement a change queue/blocker -.-)
+       if (defined $globTime) {
+               Irssi::timeout_remove($globTime);
+       } # delay the update further
+       $globTime = Irssi::timeout_add_once(GLOB_QUEUE_TIMER, 'awlHasChanged', undef);
+}
+
+# }}}
+
+
+# {{{ screen mode
+
+sub screenFullRedraw {
+       my ($window) = @_;
+       if (!ref $window or $window->{'refnum'} == Irssi::active_win->{'refnum'}) {
+               $actString = [];
+               eventChanged();
+       }
+}
+
+sub screenSize { # from nicklist.pl
+       $screenResizing = 1;
+       # fit screen
+       system 'screen -x '.$ENV{'STY'}.' -X fit';
+       # get size
+       my ($row, $col) = split ' ', `stty size`;
+       # set screen width
+       $screenWidth = $col-1;
+       $screenHeight = $row-1;
+       
+       # on some recent systems, "screen -X fit; screen -X width -w 50" doesn't work, needs a sleep in between the 2 commands
+       # so we wait a second before setting the width
+       Irssi::timeout_add_once(100, sub {
+               my ($new_irssi_width) = @_;
+               $new_irssi_width -= abs(Irssi::settings_get_int(set
+                               'block'))*$currentColumns - 1;
+               system 'screen -x '.$ENV{'STY'}.' -X width -w ' . $new_irssi_width;
+               # and then we wait another second for the resizing, and then redraw.
+               Irssi::timeout_add_once(10,sub {$screenResizing = 0; screenFullRedraw()}, []);
+       }, $screenWidth);
+}
+
+sub screenOff {
+       my ($unloadMode) = @_;
+       Irssi::signal_remove('gui print text finished' => 'screenFullRedraw');
+       Irssi::signal_remove('gui page scrolled' => 'screenFullRedraw');
+       Irssi::signal_remove('window changed' => 'screenFullRedraw');
+       Irssi::signal_remove('window changed automatic' => 'screenFullRedraw');
+       if ($unloadMode) {
+               Irssi::signal_remove('terminal resized' => 'resizeTerm');
+       }
+       system 'screen -x '.$ENV{'STY'}.' -X fit';
+}
+
+sub syncColumns {
+       return if (@$actString == 0);
+       my $temp = $currentColumns;
+       #Irssi::print("current columns $temp");
+       my $height = $screenHeight - abs(Irssi::settings_get_int(set
+                       'height_adjust'));
+       $currentColumns = int(($#$actString-1) / $height) + 1;
+       #Irssi::print("objects in actstring:".scalar(@$actString).", screen height:".
+       #       $height);
+       my $currMaxColumns = Irssi::settings_get_int(set 'columns');
+       if ($currMaxColumns > 0 and $currentColumns > $currMaxColumns) {
+               $currentColumns = $currMaxColumns;
+       }
+       elsif ($currMaxColumns < 0) {
+               $currentColumns = abs($currMaxColumns);
+       }
+       return if ($temp == $currentColumns);
+       screenSize();
+}
+
+#$needRemake = 1;
+sub resizeTerm () {
+       if ($SCREEN_MODE and !$screenResizing) {
+               $screenResizing = 1;
+               Irssi::timeout_add_once(10, 'screenSize', undef);
+       }
+       Irssi::timeout_add_once(100, 'eventChanged', undef);
+}
+
+# }}}
+
+
+# {{{ settings add
+
+Irssi::settings_add_str(setc, set 'display_nokey', '[$N]$H$C$S');
+Irssi::settings_add_str(setc, set 'display_key', '[$Q=$N]$H$C$S');
+Irssi::settings_add_str(setc, set 'display_nokey_active', '');
+Irssi::settings_add_str(setc, set 'display_key_active', '');
+Irssi::settings_add_str(setc, set 'separator', "\\ ");
+Irssi::settings_add_bool(setc, set 'prefer_name', 0);
+Irssi::settings_add_int(setc, set 'hide_data', 0);
+Irssi::settings_add_int(setc, set 'maxlines', 9);
+Irssi::settings_add_int(setc, set 'columns', 1);
+Irssi::settings_add_int(setc, set 'block', 20);
+Irssi::settings_add_bool(setc, set 'sbar_maxlength', 0);
+Irssi::settings_add_int(setc, set 'height_adjust', 2);
+Irssi::settings_add_str(setc, set 'sort', 'refnum');
+Irssi::settings_add_str(setc, set 'placement', 'bottom');
+Irssi::settings_add_int(setc, set 'position', 0);
+Irssi::settings_add_bool(setc, set 'all_disable', 0);
+Irssi::settings_add_str(setc, set 'automode', 'sbar');
+
+# }}}
+
+
+# {{{ init
+
+sub wlreset {
+       $actString = [];
+       $currentLines = 0; # 1; # mhmmmm .. we actually enable one line down there so
+                               # let's try this.
+       #update_keymap();
+       killOldStatus();
+       # Register statusbar
+       #add_statusbar(0);
+       #Irssi::command('statusbar wl0 enable');
+       my $was_screen_mode = $SCREEN_MODE;
+       if ($SCREEN_MODE = (Irssi::settings_get_str(set 'automode') =~ /screen/i)
+                       and
+               !$was_screen_mode
+       ) {
+               if (!defined $ENV{'STY'}) {
+                       Irssi::print('Screen mode can only be used in GNU screen but no '.
+                               'screen was found.', MSGLEVEL_CLIENTERROR);
+                       $SCREEN_MODE = undef;
+               }
+               else {
+                       Irssi::signal_add_last('gui print text finished' => 'screenFullRedraw');
+                       Irssi::signal_add_last('gui page scrolled' => 'screenFullRedraw');
+                       Irssi::signal_add('window changed' => 'screenFullRedraw');
+                       Irssi::signal_add('window changed automatic' => 'screenFullRedraw');
+               }
+       }
+       elsif ($was_screen_mode and !$SCREEN_MODE) {
+               screenOff();
+       }
+       $resetNeeded = ($SCREEN_MODE ?
+               "\\\n" . Irssi::settings_get_int(set 'block').
+               Irssi::settings_get_int(set 'height_adjust')
+               : "!\n" . Irssi::settings_get_str(set 'placement').
+               Irssi::settings_get_int(set 'position')).
+               Irssi::settings_get_str(set 'automode');
+       resizeTerm();
+}
+
+wlreset();
+
+# }}}
+
+
+# {{{ unload/deinit
+
+my $Unload;
+sub unload ($$$) {
+       $Unload = 1;
+       # pretend we didn't do anything ASAP
+       Irssi::timeout_add_once(10, sub { $Unload = undef; }, undef);
+}
+# last try to catch a sigsegv
+Irssi::signal_add_first('gui exit' => sub { $Unload = undef; });
+sub UNLOAD {
+       # this might well crash Irssi... try /eval /script unload someotherscript ;
+       # /quit (= SEGFAULT !)
+       if ($Unload) {
+               $actString = ['']; # syncLines(); # XXX Irssi crashes when trying to disable
+               killOldStatus();                  # XXX all statusbars ?
+               if ($SCREEN_MODE) {
+                       screenOff('unload mode');
+               }
+       }
+}
+
+# }}}
+
+
+# {{{ signals
+
+sub addPrintTextHook { # update on print text
+       return if $_[0]->{'level'} == 262144 and $_[0]->{'target'} eq ''
+                       and !defined($_[0]->{'server'});
+       if (Irssi::settings_get_str(set 'sort') =~ /^[-!]?last_line$/) {
+               Irssi::timeout_add_once(100, 'eventChanged', undef);
+       }
+}
+
+#sub _x { my ($x, $y) = @_; ($x, sub { Irssi::print('-->signal '.$x); eval "$y();"; }) }
+#sub _x { @_ }
+Irssi::signal_add_first(
+       'command script unload' => 'unload'
+);
+Irssi::signal_add_last({
+       'setup changed' => 'eventChanged',
+       'print text' => 'addPrintTextHook',
+       'terminal resized' => 'resizeTerm',
+       'setup reread' => 'wlreset',
+       'window hilight' => 'eventChanged',
+});
+Irssi::signal_add({
+       'window created' => 'eventChanged',
+       'window destroyed' => 'eventChanged',
+       'window name changed' => 'eventChanged',
+       'window refnum changed' => 'eventChanged',
+       'window changed' => 'eventChanged',
+       'window changed automatic' => 'eventChanged',
+});
+
+#Irssi::signal_add('nick mode changed', 'chanactHasChanged'); # relicts
+
+# }}}
+
+# {{{ commands
+
+
+sub runsub {
+       my ($cmd) = @_;
+       sub {
+               my ($data, $server, $item) = @_;
+               Irssi::command_runsub($cmd, $data, $server, $item);
+       };
+}
+Irssi::command_bind( setc() => runsub(setc()) );
+Irssi::command_bind( setc() . ' paste' => runsub(setc() . ' paste') );
+Irssi::command_bind(
+       setc() . ' paste on' => sub {
+               return unless $SCREEN_MODE;
+               my $was_disabled = $DISABLE_SCREEN_TEMP;
+               $DISABLE_SCREEN_TEMP = 1;
+               Irssi::print('Paste mode is now ON, '.uc(setc()).' is temporarily '.
+                            'disabled.');
+               if (!$was_disabled) {
+                       $screenResizing = 1;
+                       screenOff();
+               }
+       }
+);
+Irssi::command_bind(
+       setc() . ' paste off' => sub {
+               return unless $SCREEN_MODE;
+               my $was_disabled = $DISABLE_SCREEN_TEMP;
+               $DISABLE_SCREEN_TEMP = undef;
+               Irssi::print('Paste mode is now OFF, '.uc(setc()).' is enabled.');
+               if ($was_disabled) {
+                       $SCREEN_MODE = undef;
+                       $screenResizing = 0;
+                       wlreset();
+               }
+       }
+);
+Irssi::command_bind(
+       setc() . ' paste toggle' => sub {
+               if ($DISABLE_SCREEN_TEMP) {
+                       Irssi::command(setc() . ' paste off');
+               }
+               else {
+                       Irssi::command(setc() . ' paste on');
+               }
+       }
+);
+Irssi::command_bind(
+       setc() . ' redraw' => sub {
+               return unless $SCREEN_MODE;
+               screenFullRedraw();
+       }
+);
+               
+
+# }}}
+
+# {{{ Algorithm::LCSS module
+{
+       package Algorithm::Diff;
+       # Skip to first "=head" line for documentation.
+       use strict;
+
+       use integer;    # see below in _replaceNextLargerWith() for mod to make
+                                                # if you don't use this
+
+       # McIlroy-Hunt diff algorithm
+       # Adapted from the Smalltalk code of Mario I. Wolczko, <mario@wolczko.com>
+       # by Ned Konz, perl@bike-nomad.com
+       # Updates by Tye McQueen, http://perlmonks.org/?node=tye
+
+       # Create a hash that maps each element of $aCollection to the set of
+       # positions it occupies in $aCollection, restricted to the elements
+       # within the range of indexes specified by $start and $end.
+       # The fourth parameter is a subroutine reference that will be called to
+       # generate a string to use as a key.
+       # Additional parameters, if any, will be passed to this subroutine.
+       #
+       # my $hashRef = _withPositionsOfInInterval( \@array, $start, $end, $keyGen );
+
+       sub _withPositionsOfInInterval
+       {
+                my $aCollection = shift;    # array ref
+                my $start       = shift;
+                my $end         = shift;
+                my $keyGen      = shift;
+                my %d;
+                my $index;
+                for ( $index = $start ; $index <= $end ; $index++ )
+                {
+                         my $element = $aCollection->[$index];
+                         my $key = &$keyGen( $element, @_ );
+                         if ( exists( $d{$key} ) )
+                         {
+                                       unshift ( @{ $d{$key} }, $index );
+                         }
+                         else
+                         {
+                                       $d{$key} = [$index];
+                         }
+                }
+                return wantarray ? %d : \%d;
+       }
+
+       # Find the place at which aValue would normally be inserted into the
+       # array. If that place is already occupied by aValue, do nothing, and
+       # return undef. If the place does not exist (i.e., it is off the end of
+       # the array), add it to the end, otherwise replace the element at that
+       # point with aValue.  It is assumed that the array's values are numeric.
+       # This is where the bulk (75%) of the time is spent in this module, so
+       # try to make it fast!
+
+       sub _replaceNextLargerWith
+       {
+                my ( $array, $aValue, $high ) = @_;
+                $high ||= $#$array;
+
+                # off the end?
+                if ( $high == -1 || $aValue > $array->[-1] )
+                {
+                         push ( @$array, $aValue );
+                         return $high + 1;
+                }
+
+                # binary search for insertion point...
+                my $low = 0;
+                my $index;
+                my $found;
+                while ( $low <= $high )
+                {
+                         $index = ( $high + $low ) / 2;
+
+                         # $index = int(( $high + $low ) / 2);  # without 'use integer'
+                         $found = $array->[$index];
+
+                         if ( $aValue == $found )
+                         {
+                                       return undef;
+                         }
+                         elsif ( $aValue > $found )
+                         {
+                                       $low = $index + 1;
+                         }
+                         else
+                         {
+                                       $high = $index - 1;
+                         }
+                }
+
+                # now insertion point is in $low.
+                $array->[$low] = $aValue;    # overwrite next larger
+                return $low;
+       }
+
+       # This method computes the longest common subsequence in $a and $b.
+
+       # Result is array or ref, whose contents is such that
+       #   $a->[ $i ] == $b->[ $result[ $i ] ]
+       # foreach $i in ( 0 .. $#result ) if $result[ $i ] is defined.
+
+       # An additional argument may be passed; this is a hash or key generating
+       # function that should return a string that uniquely identifies the given
+       # element.  It should be the case that if the key is the same, the elements
+       # will compare the same. If this parameter is undef or missing, the key
+       # will be the element as a string.
+
+       # By default, comparisons will use "eq" and elements will be turned into keys
+       # using the default stringizing operator '""'.
+
+       # Additional parameters, if any, will be passed to the key generation
+       # routine.
+
+       sub _longestCommonSubsequence
+       {
+                my $a        = shift;    # array ref or hash ref
+                my $b        = shift;    # array ref or hash ref
+                my $counting = shift;    # scalar
+                my $keyGen   = shift;    # code ref
+                my $compare;             # code ref
+
+                if ( ref($a) eq 'HASH' )
+                {                        # prepared hash must be in $b
+                         my $tmp = $b;
+                         $b = $a;
+                         $a = $tmp;
+                }
+
+                # Check for bogus (non-ref) argument values
+                if ( !ref($a) || !ref($b) )
+                {
+                         my @callerInfo = caller(1);
+                         die 'error: must pass array or hash references to ' . $callerInfo[3];
+                }
+
+                # set up code refs
+                # Note that these are optimized.
+                if ( !defined($keyGen) )    # optimize for strings
+                {
+                         $keyGen = sub { $_[0] };
+                         $compare = sub { my ( $a, $b ) = @_; $a eq $b };
+                }
+                else
+                {
+                         $compare = sub {
+                                       my $a = shift;
+                                       my $b = shift;
+                                       &$keyGen( $a, @_ ) eq &$keyGen( $b, @_ );
+                         };
+                }
+
+                my ( $aStart, $aFinish, $matchVector ) = ( 0, $#$a, [] );
+                my ( $prunedCount, $bMatches ) = ( 0, {} );
+
+                if ( ref($b) eq 'HASH' )    # was $bMatches prepared for us?
+                {
+                         $bMatches = $b;
+                }
+                else
+                {
+                         my ( $bStart, $bFinish ) = ( 0, $#$b );
+
+                         # First we prune off any common elements at the beginning
+                         while ( $aStart <= $aFinish
+                                       and $bStart <= $bFinish
+                                       and &$compare( $a->[$aStart], $b->[$bStart], @_ ) )
+                         {
+                                       $matchVector->[ $aStart++ ] = $bStart++;
+                                       $prunedCount++;
+                         }
+
+                         # now the end
+                         while ( $aStart <= $aFinish
+                                       and $bStart <= $bFinish
+                                       and &$compare( $a->[$aFinish], $b->[$bFinish], @_ ) )
+                         {
+                                       $matchVector->[ $aFinish-- ] = $bFinish--;
+                                       $prunedCount++;
+                         }
+
+                         # Now compute the equivalence classes of positions of elements
+                         $bMatches =
+                                _withPositionsOfInInterval( $b, $bStart, $bFinish, $keyGen, @_ );
+                }
+                my $thresh = [];
+                my $links  = [];
+
+                my ( $i, $ai, $j, $k );
+                for ( $i = $aStart ; $i <= $aFinish ; $i++ )
+                {
+                         $ai = &$keyGen( $a->[$i], @_ );
+                         if ( exists( $bMatches->{$ai} ) )
+                         {
+                                       $k = 0;
+                                       for $j ( @{ $bMatches->{$ai} } )
+                                       {
+
+                                                # optimization: most of the time this will be true
+                                                if ( $k and $thresh->[$k] > $j and $thresh->[ $k - 1 ] < $j )
+                                                {
+                                                         $thresh->[$k] = $j;
+                                                }
+                                                else
+                                                {
+                                                         $k = _replaceNextLargerWith( $thresh, $j, $k );
+                                                }
+
+                                                # oddly, it's faster to always test this (CPU cache?).
+                                                if ( defined($k) )
+                                                {
+                                                         $links->[$k] =
+                                                                [ ( $k ? $links->[ $k - 1 ] : undef ), $i, $j ];
+                                                }
+                                       }
+                         }
+                }
+
+                if (@$thresh)
+                {
+                         return $prunedCount + @$thresh if $counting;
+                         for ( my $link = $links->[$#$thresh] ; $link ; $link = $link->[0] )
+                         {
+                                       $matchVector->[ $link->[1] ] = $link->[2];
+                         }
+                }
+                elsif ($counting)
+                {
+                         return $prunedCount;
+                }
+
+                return wantarray ? @$matchVector : $matchVector;
+       }
+
+       sub traverse_sequences
+       {
+                my $a                 = shift;          # array ref
+                my $b                 = shift;          # array ref
+                my $callbacks         = shift || {};
+                my $keyGen            = shift;
+                my $matchCallback     = $callbacks->{'MATCH'} || sub { };
+                my $discardACallback  = $callbacks->{'DISCARD_A'} || sub { };
+                my $finishedACallback = $callbacks->{'A_FINISHED'};
+                my $discardBCallback  = $callbacks->{'DISCARD_B'} || sub { };
+                my $finishedBCallback = $callbacks->{'B_FINISHED'};
+                my $matchVector = _longestCommonSubsequence( $a, $b, 0, $keyGen, @_ );
+
+                # Process all the lines in @$matchVector
+                my $lastA = $#$a;
+                my $lastB = $#$b;
+                my $bi    = 0;
+                my $ai;
+
+                for ( $ai = 0 ; $ai <= $#$matchVector ; $ai++ )
+                {
+                         my $bLine = $matchVector->[$ai];
+                         if ( defined($bLine) )    # matched
+                         {
+                                       &$discardBCallback( $ai, $bi++, @_ ) while $bi < $bLine;
+                                       &$matchCallback( $ai,    $bi++, @_ );
+                         }
+                         else
+                         {
+                                       &$discardACallback( $ai, $bi, @_ );
+                         }
+                }
+
+                # The last entry (if any) processed was a match.
+                # $ai and $bi point just past the last matching lines in their sequences.
+
+                while ( $ai <= $lastA or $bi <= $lastB )
+                {
+
+                         # last A?
+                         if ( $ai == $lastA + 1 and $bi <= $lastB )
+                         {
+                                       if ( defined($finishedACallback) )
+                                       {
+                                                &$finishedACallback( $lastA, @_ );
+                                                $finishedACallback = undef;
+                                       }
+                                       else
+                                       {
+                                                &$discardBCallback( $ai, $bi++, @_ ) while $bi <= $lastB;
+                                       }
+                         }
+
+                         # last B?
+                         if ( $bi == $lastB + 1 and $ai <= $lastA )
+                         {
+                                       if ( defined($finishedBCallback) )
+                                       {
+                                                &$finishedBCallback( $lastB, @_ );
+                                                $finishedBCallback = undef;
+                                       }
+                                       else
+                                       {
+                                                &$discardACallback( $ai++, $bi, @_ ) while $ai <= $lastA;
+                                       }
+                         }
+
+                         &$discardACallback( $ai++, $bi, @_ ) if $ai <= $lastA;
+                         &$discardBCallback( $ai, $bi++, @_ ) if $bi <= $lastB;
+                }
+
+                return 1;
+       }
+
+       sub traverse_balanced
+       {
+                my $a                 = shift;              # array ref
+                my $b                 = shift;              # array ref
+                my $callbacks         = shift || {};
+                my $keyGen            = shift;
+                my $matchCallback     = $callbacks->{'MATCH'} || sub { };
+                my $discardACallback  = $callbacks->{'DISCARD_A'} || sub { };
+                my $discardBCallback  = $callbacks->{'DISCARD_B'} || sub { };
+                my $changeCallback    = $callbacks->{'CHANGE'};
+                my $matchVector = _longestCommonSubsequence( $a, $b, 0, $keyGen, @_ );
+
+                # Process all the lines in match vector
+                my $lastA = $#$a;
+                my $lastB = $#$b;
+                my $bi    = 0;
+                my $ai    = 0;
+                my $ma    = -1;
+                my $mb;
+
+                while (1)
+                {
+
+                         # Find next match indices $ma and $mb
+                         do {
+                                       $ma++;
+                         } while(
+                                                $ma <= $#$matchVector
+                                       &&  !defined $matchVector->[$ma]
+                         );
+
+                         last if $ma > $#$matchVector;    # end of matchVector?
+                         $mb = $matchVector->[$ma];
+
+                         # Proceed with discard a/b or change events until
+                         # next match
+                         while ( $ai < $ma || $bi < $mb )
+                         {
+
+                                       if ( $ai < $ma && $bi < $mb )
+                                       {
+
+                                                # Change
+                                                if ( defined $changeCallback )
+                                                {
+                                                         &$changeCallback( $ai++, $bi++, @_ );
+                                                }
+                                                else
+                                                {
+                                                         &$discardACallback( $ai++, $bi, @_ );
+                                                         &$discardBCallback( $ai, $bi++, @_ );
+                                                }
+                                       }
+                                       elsif ( $ai < $ma )
+                                       {
+                                                &$discardACallback( $ai++, $bi, @_ );
+                                       }
+                                       else
+                                       {
+
+                                                # $bi < $mb
+                                                &$discardBCallback( $ai, $bi++, @_ );
+                                       }
+                         }
+
+                         # Match
+                         &$matchCallback( $ai++, $bi++, @_ );
+                }
+
+                while ( $ai <= $lastA || $bi <= $lastB )
+                {
+                         if ( $ai <= $lastA && $bi <= $lastB )
+                         {
+
+                                       # Change
+                                       if ( defined $changeCallback )
+                                       {
+                                                &$changeCallback( $ai++, $bi++, @_ );
+                                       }
+                                       else
+                                       {
+                                                &$discardACallback( $ai++, $bi, @_ );
+                                                &$discardBCallback( $ai, $bi++, @_ );
+                                       }
+                         }
+                         elsif ( $ai <= $lastA )
+                         {
+                                       &$discardACallback( $ai++, $bi, @_ );
+                         }
+                         else
+                         {
+
+                                       # $bi <= $lastB
+                                       &$discardBCallback( $ai, $bi++, @_ );
+                         }
+                }
+
+                return 1;
+       }
+
+       sub prepare
+       {
+                my $a       = shift;    # array ref
+                my $keyGen  = shift;    # code ref
+
+                # set up code ref
+                $keyGen = sub { $_[0] } unless defined($keyGen);
+
+                return scalar _withPositionsOfInInterval( $a, 0, $#$a, $keyGen, @_ );
+       }
+
+       sub LCS
+       {
+                my $a = shift;                  # array ref
+                my $b = shift;                  # array ref or hash ref
+                my $matchVector = _longestCommonSubsequence( $a, $b, 0, @_ );
+                my @retval;
+                my $i;
+                for ( $i = 0 ; $i <= $#$matchVector ; $i++ )
+                {
+                         if ( defined( $matchVector->[$i] ) )
+                         {
+                                       push ( @retval, $a->[$i] );
+                         }
+                }
+                return wantarray ? @retval : \@retval;
+       }
+
+       sub LCS_length
+       {
+                my $a = shift;                          # array ref
+                my $b = shift;                          # array ref or hash ref
+                return _longestCommonSubsequence( $a, $b, 1, @_ );
+       }
+
+       sub LCSidx
+       {
+                my $a= shift @_;
+                my $b= shift @_;
+                my $match= _longestCommonSubsequence( $a, $b, 0, @_ );
+                my @am= grep defined $match->[$_], 0..$#$match;
+                my @bm= @{$match}[@am];
+                return \@am, \@bm;
+       }
+
+       sub compact_diff
+       {
+                my $a= shift @_;
+                my $b= shift @_;
+                my( $am, $bm )= LCSidx( $a, $b, @_ );
+                my @cdiff;
+                my( $ai, $bi )= ( 0, 0 );
+                push @cdiff, $ai, $bi;
+                while( 1 ) {
+                         while(  @$am  &&  $ai == $am->[0]  &&  $bi == $bm->[0]  ) {
+                                       shift @$am;
+                                       shift @$bm;
+                                       ++$ai, ++$bi;
+                         }
+                         push @cdiff, $ai, $bi;
+                         last   if  ! @$am;
+                         $ai = $am->[0];
+                         $bi = $bm->[0];
+                         push @cdiff, $ai, $bi;
+                }
+                push @cdiff, 0+@$a, 0+@$b
+                         if  $ai < @$a || $bi < @$b;
+                return wantarray ? @cdiff : \@cdiff;
+       }
+
+       sub diff
+       {
+                my $a      = shift;    # array ref
+                my $b      = shift;    # array ref
+                my $retval = [];
+                my $hunk   = [];
+                my $discard = sub {
+                         push @$hunk, [ '-', $_[0], $a->[ $_[0] ] ];
+                };
+                my $add = sub {
+                         push @$hunk, [ '+', $_[1], $b->[ $_[1] ] ];
+                };
+                my $match = sub {
+                         push @$retval, $hunk
+                                       if 0 < @$hunk;
+                         $hunk = []
+                };
+                traverse_sequences( $a, $b,
+                         { MATCH => $match, DISCARD_A => $discard, DISCARD_B => $add }, @_ );
+                &$match();
+                return wantarray ? @$retval : $retval;
+       }
+
+       sub sdiff
+       {
+                my $a      = shift;    # array ref
+                my $b      = shift;    # array ref
+                my $retval = [];
+                my $discard = sub { push ( @$retval, [ '-', $a->[ $_[0] ], "" ] ) };
+                my $add = sub { push ( @$retval, [ '+', "", $b->[ $_[1] ] ] ) };
+                my $change = sub {
+                         push ( @$retval, [ 'c', $a->[ $_[0] ], $b->[ $_[1] ] ] );
+                };
+                my $match = sub {
+                         push ( @$retval, [ 'u', $a->[ $_[0] ], $b->[ $_[1] ] ] );
+                };
+                traverse_balanced(
+                         $a,
+                         $b,
+                         {
+                                       MATCH     => $match,
+                                       DISCARD_A => $discard,
+                                       DISCARD_B => $add,
+                                       CHANGE    => $change,
+                         },
+                         @_
+                );
+                return wantarray ? @$retval : $retval;
+       }
+
+       ########################################
+       my $Root= __PACKAGE__;
+       package Algorithm::Diff::_impl;
+       use strict;
+
+       sub _Idx()  { 0 } # $me->[_Idx]: Ref to array of hunk indices
+                                       # 1   # $me->[1]: Ref to first sequence
+                                       # 2   # $me->[2]: Ref to second sequence
+       sub _End()  { 3 } # $me->[_End]: Diff between forward and reverse pos
+       sub _Same() { 4 } # $me->[_Same]: 1 if pos 1 contains unchanged items
+       sub _Base() { 5 } # $me->[_Base]: Added to range's min and max
+       sub _Pos()  { 6 } # $me->[_Pos]: Which hunk is currently selected
+       sub _Off()  { 7 } # $me->[_Off]: Offset into _Idx for current position
+       sub _Min() { -2 } # Added to _Off to get min instead of max+1
+
+       sub Die
+       {
+                require Carp;
+                Carp::confess( @_ );
+       }
+
+       sub _ChkPos
+       {
+                my( $me )= @_;
+                return   if  $me->[_Pos];
+                my $meth= ( caller(1) )[3];
+                Die( "Called $meth on 'reset' object" );
+       }
+
+       sub _ChkSeq
+       {
+                my( $me, $seq )= @_;
+                return $seq + $me->[_Off]
+                         if  1 == $seq  ||  2 == $seq;
+                my $meth= ( caller(1) )[3];
+                Die( "$meth: Invalid sequence number ($seq); must be 1 or 2" );
+       }
+
+       sub getObjPkg
+       {
+                my( $us )= @_;
+                return ref $us   if  ref $us;
+                return $us . "::_obj";
+       }
+
+       sub new
+       {
+                my( $us, $seq1, $seq2, $opts ) = @_;
+                my @args;
+                for( $opts->{keyGen} ) {
+                         push @args, $_   if  $_;
+                }
+                for( $opts->{keyGenArgs} ) {
+                         push @args, @$_   if  $_;
+                }
+                my $cdif= Algorithm::Diff::compact_diff( $seq1, $seq2, @args );
+                my $same= 1;
+                if(  0 == $cdif->[2]  &&  0 == $cdif->[3]  ) {
+                         $same= 0;
+                         splice @$cdif, 0, 2;
+                }
+                my @obj= ( $cdif, $seq1, $seq2 );
+                $obj[_End] = (1+@$cdif)/2;
+                $obj[_Same] = $same;
+                $obj[_Base] = 0;
+                my $me = bless \@obj, $us->getObjPkg();
+                $me->Reset( 0 );
+                return $me;
+       }
+
+       sub Reset
+       {
+                my( $me, $pos )= @_;
+                $pos= int( $pos || 0 );
+                $pos += $me->[_End]
+                         if  $pos < 0;
+                $pos= 0
+                         if  $pos < 0  ||  $me->[_End] <= $pos;
+                $me->[_Pos]= $pos || !1;
+                $me->[_Off]= 2*$pos - 1;
+                return $me;
+       }
+
+       sub Base
+       {
+                my( $me, $base )= @_;
+                my $oldBase= $me->[_Base];
+                $me->[_Base]= 0+$base   if  defined $base;
+                return $oldBase;
+       }
+
+       sub Copy
+       {
+                my( $me, $pos, $base )= @_;
+                my @obj= @$me;
+                my $you= bless \@obj, ref($me);
+                $you->Reset( $pos )   if  defined $pos;
+                $you->Base( $base );
+                return $you;
+       }
+
+       sub Next {
+                my( $me, $steps )= @_;
+                $steps= 1   if  ! defined $steps;
+                if( $steps ) {
+                         my $pos= $me->[_Pos];
+                         my $new= $pos + $steps;
+                         $new= 0   if  $pos  &&  $new < 0;
+                         $me->Reset( $new )
+                }
+                return $me->[_Pos];
+       }
+
+       sub Prev {
+                my( $me, $steps )= @_;
+                $steps= 1   if  ! defined $steps;
+                my $pos= $me->Next(-$steps);
+                $pos -= $me->[_End]   if  $pos;
+                return $pos;
+       }
+
+       sub Diff {
+                my( $me )= @_;
+                $me->_ChkPos();
+                return 0   if  $me->[_Same] == ( 1 & $me->[_Pos] );
+                my $ret= 0;
+                my $off= $me->[_Off];
+                for my $seq ( 1, 2 ) {
+                         $ret |= $seq
+                                       if  $me->[_Idx][ $off + $seq + _Min ]
+                                       <   $me->[_Idx][ $off + $seq ];
+                }
+                return $ret;
+       }
+
+       sub Min {
+                my( $me, $seq, $base )= @_;
+                $me->_ChkPos();
+                my $off= $me->_ChkSeq($seq);
+                $base= $me->[_Base] if !defined $base;
+                return $base + $me->[_Idx][ $off + _Min ];
+       }
+
+       sub Max {
+                my( $me, $seq, $base )= @_;
+                $me->_ChkPos();
+                my $off= $me->_ChkSeq($seq);
+                $base= $me->[_Base] if !defined $base;
+                return $base + $me->[_Idx][ $off ] -1;
+       }
+
+       sub Range {
+                my( $me, $seq, $base )= @_;
+                $me->_ChkPos();
+                my $off = $me->_ChkSeq($seq);
+                if( !wantarray ) {
+                         return  $me->[_Idx][ $off ]
+                                       -   $me->[_Idx][ $off + _Min ];
+                }
+                $base= $me->[_Base] if !defined $base;
+                return  ( $base + $me->[_Idx][ $off + _Min ] )
+                         ..  ( $base + $me->[_Idx][ $off ] - 1 );
+       }
+
+       sub Items {
+                my( $me, $seq )= @_;
+                $me->_ChkPos();
+                my $off = $me->_ChkSeq($seq);
+                if( !wantarray ) {
+                         return  $me->[_Idx][ $off ]
+                                       -   $me->[_Idx][ $off + _Min ];
+                }
+                return
+                         @{$me->[$seq]}[
+                                                $me->[_Idx][ $off + _Min ]
+                                       ..  ( $me->[_Idx][ $off ] - 1 )
+                         ];
+       }
+
+       sub Same {
+                my( $me )= @_;
+                $me->_ChkPos();
+                return wantarray ? () : 0
+                         if  $me->[_Same] != ( 1 & $me->[_Pos] );
+                return $me->Items(1);
+       }
+
+       my %getName;
+                %getName= (
+                         same => \&Same,
+                         diff => \&Diff,
+                         base => \&Base,
+                         min  => \&Min,
+                         max  => \&Max,
+                         range=> \&Range,
+                         items=> \&Items, # same thing
+                );
+
+       sub Get
+       {
+                my $me= shift @_;
+                $me->_ChkPos();
+                my @value;
+                for my $arg (  @_  ) {
+                         for my $word (  split ' ', $arg  ) {
+                                       my $meth;
+                                       if(     $word !~ /^(-?\d+)?([a-zA-Z]+)([12])?$/
+                                                ||  not  $meth= $getName{ lc $2 }
+                                       ) {
+                                                Die( $Root, ", Get: Invalid request ($word)" );
+                                       }
+                                       my( $base, $name, $seq )= ( $1, $2, $3 );
+                                       push @value, scalar(
+                                                4 == length($name)
+                                                         ? $meth->( $me )
+                                                         : $meth->( $me, $seq, $base )
+                                       );
+                         }
+                }
+                if(  wantarray  ) {
+                         return @value;
+                } elsif(  1 == @value  ) {
+                         return $value[0];
+                }
+                Die( 0+@value, " values requested from ",
+                         $Root, "'s Get in scalar context" );
+       }
+
+
+       my $Obj= getObjPkg($Root);
+       no strict 'refs';
+
+       for my $meth (  qw( new getObjPkg )  ) {
+                *{$Root."::".$meth} = \&{$meth};
+                *{$Obj ."::".$meth} = \&{$meth};
+       }
+       for my $meth (  qw(
+                Next Prev Reset Copy Base Diff
+                Same Items Range Min Max Get
+                _ChkPos _ChkSeq
+       )  ) {
+                *{$Obj."::".$meth} = \&{$meth};
+       }
+
+};
+{
+       package Algorithm::LCSS;
+
+       use strict;
+       {
+               no strict 'refs';
+               *traverse_sequences = \&Algorithm::Diff::traverse_sequences;
+       }
+
+       sub _tokenize { [split //, $_[0]] }
+
+       sub CSS {
+                my $is_array = ref $_[0] eq 'ARRAY' ? 1 : 0;
+                my ( $seq1, $seq2, @match, $from_match );
+                my $i = 0;
+                if ( $is_array ) {
+                         $seq1 = $_[0];
+                         $seq2 = $_[1];
+                         traverse_sequences( $seq1, $seq2, {
+                                       MATCH => sub { push @{$match[$i]}, $seq1->[$_[0]]; $from_match = 1 },
+                                       DISCARD_A => sub { do{$i++; $from_match = 0} if $from_match },
+                                       DISCARD_B => sub { do{$i++; $from_match = 0} if $from_match },
+                         });
+                }
+                else {
+                         $seq1 = _tokenize($_[0]);
+                         $seq2 = _tokenize($_[1]);
+                         traverse_sequences( $seq1, $seq2, {
+                                       MATCH => sub { $match[$i] .= $seq1->[$_[0]]; $from_match = 1 },
+                                       DISCARD_A => sub { do{$i++; $from_match = 0} if $from_match },
+                                       DISCARD_B => sub { do{$i++; $from_match = 0} if $from_match },
+                         });
+                }
+         return \@match;
+       }
+
+       sub CSS_Sorted {
+                my $match = CSS(@_);
+                if ( ref $_[0] eq 'ARRAY' ) {
+                        @$match = map{$_->[0]}sort{$b->[1]<=>$a->[1]}map{[$_,scalar(@$_)]}@$match
+                }
+                else {
+                        @$match = map{$_->[0]}sort{$b->[1]<=>$a->[1]}map{[$_,length($_)]}@$match
+                }
+         return $match;
+       }
+
+       sub LCSS {
+                my $is_array = ref $_[0] eq 'ARRAY' ? 1 : 0;
+                my $css = CSS(@_);
+                my $index;
+                my $length = 0;
+                if ( $is_array ) {
+                         for( my $i = 0; $i < @$css; $i++ ) {
+                                       next unless @{$css->[$i]}>$length;
+                                       $index = $i;
+                                       $length = @{$css->[$i]};
+                         }
+                }
+                else {
+                         for( my $i = 0; $i < @$css; $i++ ) {
+                                       next unless length($css->[$i])>$length;
+                                       $index = $i;
+                                       $length = length($css->[$i]);
+                         }
+                }
+         return $css->[$index];
+       }
+
+};
+# }}}
+#{{{ Class::Classless module
+{
+       package Class::Classless;
+       use strict;
+       use vars qw(@ISA);
+       use Carp;
+
+       @ISA = ();
+
+       ###########################################################################
+
+       @Class::Classless::X::ISA = ();
+
+       ###########################################################################
+       ###########################################################################
+
+       sub Class::Classless::X::AUTOLOAD {
+         # This's the big dispatcher.
+         
+         my $it = shift @_;
+         my $m =  ($Class::Classless::X::AUTOLOAD =~ m/([^:]+)$/s ) 
+                                        ? $1 : $Class::Classless::X::AUTOLOAD;
+
+         croak "Can't call Class::Classless methods (like $m) without an object"
+                unless ref $it;  # sanity, basically.
+
+         my $prevstate;
+         $prevstate = ${shift @_}
+               if scalar(@_) && defined($_[0]) &&
+                       ref($_[0]) eq 'Class::Classless::CALLSTATE::SHIMMY'
+         ;   # A shim!  we were called via $callstate->NEXT
+
+         my $no_fail = $prevstate ? $prevstate->[3] : undef;
+         my $i       = $prevstate ? ($prevstate->[1] + 1) : 0;
+               # where to start scanning
+         my $lineage;
+
+         # Get the linearization of the ISA tree
+         if($prevstate) {
+                $lineage = $prevstate->[2];
+         } elsif(defined $it->{'ISA_CACHE'} and ref $it->{'ISA_CACHE'} ){
+                $lineage = $it->{'ISA_CACHE'};
+         } else {
+                $lineage = [ &Class::Classless::X::ISA_TREE($it) ];
+         }
+
+         # Was:
+         #my @lineage =
+         #  $prevstate ? @{$prevstate->[2]}
+         #             : &Class::Classless::X::ISA_TREE($it);
+         # # Get the linearization of the ISA tree
+         # # ISA-memoization happens in the ISA_TREE function.
+         
+         for(; $i < @$lineage; ++$i) {
+
+                if( !defined($no_fail) and exists($lineage->[$i]{'NO_FAIL'}) ) {
+                       $no_fail = ($lineage->[$i]{'NO_FAIL'} || 0);
+                       # so the first NO_FAIL sets it
+                }
+
+                if(     ref($lineage->[$i]{'METHODS'}     || 0)  # sanity
+                       && exists($lineage->[$i]{'METHODS'}{$m})
+                ){
+                       # We found what we were after.  Now see what to do with it.
+                       my $v = $lineage->[$i]{'METHODS'}{$m};
+                       return $v unless defined $v and ref $v;
+
+                       if(ref($v) eq 'CODE') { # normal case, I expect!
+                         # Used to have copying of the arglist here.
+                         #  But it was apparently useless, so I deleted it
+                         unshift @_, 
+                                $it,                   # $_[0]    -- target object
+                                # a NEW callstate
+                                bless([$m, $i, $lineage, $no_fail, $prevstate ? 1 : 0],
+                                                'Class::Classless::CALLSTATE'
+                                               ),                # $_[1]    -- the callstate
+                         ;
+                         goto &{ $v }; # yes, magic goto!  bimskalabim!
+                       }
+                       return @$v if ref($v) eq '_deref_array';
+                       return $$v if ref($v) eq '_deref_scalar';
+                       return $v; # fallthru
+                }
+         }
+
+         if($m eq 'DESTROY') { # mitigate DESTROY-lookup failure at global destruction
+                # should be impossible
+         } else {
+                if($no_fail || 0) {
+                       return;
+                }
+                croak "Can't find ", $prevstate ? 'NEXT method' : 'method',
+                                " $m in ", $it->{'NAME'} || $it,
+                                " or any ancestors\n";
+         }
+       }
+
+       ###########################################################################
+       ###########################################################################
+
+       sub Class::Classless::X::DESTROY {
+         # noop
+       }
+
+       ###########################################################################
+       sub Class::Classless::X::ISA_TREE {
+         # The linearizer!
+         # Returns the search path for $_[0], starting with $_[0]
+         # Possibly memoized.
+
+         # I stopped being able to understand this algorithm about five
+         #  minutes after I wrote it.
+         use strict;
+         
+         my $set_cache = 0; # flag to set the cache on the way out
+         
+         if(exists($_[0]{'ISA_CACHE'})) {
+                return    @{$_[0]{'ISA_CACHE'}}
+                 if defined $_[0]{'ISA_CACHE'}
+                         and ref $_[0]{'ISA_CACHE'};
+                 
+                # Otherwise, if exists but is not a ref, it's a signal that it should
+                #  be replaced at the earliest, with a listref
+                $set_cache = 1;
+         }
+         
+         my $has_mi = 0; # set to 0 on the first node we see with 2 parents!
+         # First, just figure out what's in the tree.
+         my %last_child = ($_[0] => 1); # as if already seen
+
+         # if $last_child{$x} == $y, that means:
+         #  1) incidentally, we've passed the node $x before.
+         #  2) $x is the last child of $y,
+         #     so that means that $y can be pushed to the stack only after
+         #      we've pushed $x to the stack.
+         
+         my @tree_nodes;
+         {
+                my $current;
+                my @in_stack = ($_[0]);
+                while(@in_stack) {
+                       next unless
+                        defined($current = shift @in_stack)
+                        && ref($current) # sanity
+                        && ref($current->{'PARENTS'} || 0) # sanity
+                       ;
+
+                       push @tree_nodes, $current;
+
+                       $has_mi = 1 if @{$current->{'PARENTS'}} > 1;
+                       unshift
+                         @in_stack,
+                         map {
+                                if(exists $last_child{$_}) { # seen before!
+                                       $last_child{$_} = $current;
+                                       (); # seen -- don't re-explore
+                                } else { # first time seen
+                                       $last_child{$_} = $current;
+                                       $_; # first time seen -- explore now
+                                }
+                         }
+                         @{$current->{'PARENTS'}}
+                       ;
+                }
+
+                # If there was no MI, then that first scan was sufficient.
+                unless($has_mi) {
+                       $_[0]{'ISA_CACHE'} = \@tree_nodes if $set_cache;
+                       return @tree_nodes;
+                }
+
+                # Otherwise, toss this list and rescan, consulting %last_child
+         }
+
+         # $last_child{$parent} holds the last (or only) child of $parent
+         # in this tree.  When walking the tree this time, only that
+         # child is authorized to put its parent on the @in_stack.
+         # And that's the only way a node can get added to @in_stack,
+         # except for $_[0] (the start node) being there at the beginning.
+
+         # Now, walk again, but this time exploring parents the LAST
+         # time seen in the tree, not the first.
+
+         my @out;
+         {
+                my $current;
+                my @in_stack = ($_[0]);
+                while(@in_stack) {
+                       next unless defined($current = shift @in_stack) && ref($current);
+                       push @out, $current; # finally.
+                       unshift
+                         @in_stack,
+                         grep(
+                                (
+                                       defined($_) # sanity
+                                       && ref($_)  # sanity
+                                       && $last_child{$_} eq $current,
+                                ),
+                                # I'm lastborn (or onlyborn) of this parent
+                                # so it's OK to explore now
+                                @{$current->{'PARENTS'}}
+                         )
+                        if ref($current->{'PARENTS'} || 0) # sanity
+                       ;
+                }
+
+                unless(scalar(@out) == scalar(keys(%last_child))) {
+                       # the counts should be equal
+                       my %good_ones;
+                       @good_ones{@out} = ();
+                       croak
+                         "ISA tree for " .
+                         ($_[0]{'NAME'} || $_[0]) .
+                         " is apparently cyclic, probably involving the nodes " .
+                         nodelist( grep { ref($_) && !exists $good_ones{$_} }
+                                values(%last_child) )
+                         . "\n";
+                }
+         }
+         #print "Contents of out: ", nodelist(@out), "\n";
+         
+         $_[0]{'ISA_CACHE'} = \@out if $set_cache;
+         return @out;
+       }
+
+       ###########################################################################
+
+       sub Class::Classless::X::can { # NOT like UNIVERSAL::can ...
+         # return 1 if $it is capable of the method given -- otherwise 0
+         my($it, $m) = @_[0,1];
+         return undef unless ref $it;
+
+         croak "undef is not a valid method name"       unless defined($m);
+         croak "null-string is not a valid method name" unless length($m);
+
+         foreach my $o (&Class::Classless::X::ISA_TREE($it)) {
+                return 1
+                 if  ref($o->{'METHODS'} || 0)   # sanity
+                       && exists $o->{'METHODS'}{$m};
+         }
+
+         return 0;
+       }
+
+
+       ###########################################################################
+
+       sub Class::Classless::X::isa { # Like UNIVERSAL::isa
+         # Returns true for $X->isa($Y) iff $Y is $X or is an ancestor of $X.
+
+         return unless ref($_[0]) && ref($_[1]);
+         return scalar(grep {$_ eq $_[1]} &Class::Classless::X::ISA_TREE($_[0])); 
+       }
+
+       ###########################################################################
+
+       sub nodelist { join ', ', map { "" . ($_->{'NAME'} || $_) . ""} @_ }
+
+       ###########################################################################
+       ###########################################################################
+       ###########################################################################
+       # Methods for the CALLSTATE class.
+       #  Basically, CALLSTATE objects represent the state of the dispatcher,
+       #   frozen at the moment when the method call was dispatched to the
+       #   appropriate sub.
+       #  In the grand scheme of things, this needn't be a class -- I could
+       #   have just made the callstate data-object be a hash with documented
+       #   keys, or a closure that responded to only certain parameters,
+       #   etc.  But I like it this way.  And I like being able to say simply
+       #   $cs->NEXT
+       #  Yes, these are a bit cryptically written, but it's behoovy for
+       #   them to be very very efficient.
+
+       @Class::Classless::ISA = ();
+       sub Class::Classless::CALLSTATE::found_name { $_[0][0] }
+               #  the method name called and found
+       sub Class::Classless::CALLSTATE::found_depth { $_[0][1] }
+               #  my depth in the lineage
+       sub Class::Classless::CALLSTATE::lineage { @{$_[0][2]} }
+               #  my lineage
+       sub Class::Classless::CALLSTATE::target { $_[0][2][  0          ] }
+               #  the object that's the target -- same as $_[0] for the method called
+       sub Class::Classless::CALLSTATE::home   { $_[0][2][  $_[0][1]   ] }
+               #  the object I was found in
+       sub Class::Classless::CALLSTATE::sub_found {
+         $_[0][2][  $_[0][1]   ]{'METHODS'}{ $_[0][0] }
+       }  #  the routine called
+
+       sub Class::Classless::CALLSTATE::no_fail          {  $_[0][3]         }
+       sub Class::Classless::CALLSTATE::set_no_fail_true {  $_[0][3] = 1     }
+       sub Class::Classless::CALLSTATE::set_fail_false   {  $_[0][3] = 0     }
+       sub Class::Classless::CALLSTATE::set_fail_undef   {  $_[0][3] = undef }
+
+       sub Class::Classless::CALLSTATE::via_next         {  $_[0][4] }
+
+       sub Class::Classless::CALLSTATE::NEXT {
+         #croak "NEXT needs at least one argument: \$cs->NEXT('method'...)"
+         # unless @_ > 1;
+               # no longer true.
+         my $cs = shift @_;
+         my $m  = shift @_; # which may be (or come out) undef...
+         $m = $cs->[0] unless defined $m; #  the method name called and found
+
+         ($cs->[2][0])->$m(
+                bless( \$cs, 'Class::Classless::CALLSTATE::SHIMMY' ),
+                @_
+         );
+       }
+
+       ###########################################################################
+};
+#}}}
+
+###############
+###
+#
+# {{{ *** C h a n g e l o g ***
+#
+# 0.6ca
+# - add screen support (from nicklist.pl)
+# - rename to adv_windowlist.pl (advanced window list) since it isn't just a
+#   window list status bar (wlstat) anymore
+# - names can now have a max length and window names can be used
+# - fixed a bug with block display in screen mode and statusbar mode
+# - added space handling to ir_fe and removed it again
+# - now handling formats on my own
+# - added warning about missing sb_act_none abstract leading to
+# - display*active settings
+# - added warning about the bug in awl_display_(no)key_active settings
+#
+# 0.5d
+# - add setting to also hide the last statusbar if empty (awl_all_disable)
+# - reverted to old utf8 code to also calculate broken utf8 length correctly
+# - simplified dealing with statusbars in wlreset
+# - added a little tweak for the renamed term_type somewhere after Irssi 0.8.9
+# - fixed bug in handling channel #$$
+# - typo on line 200 spotted by f0rked
+# - reset background colour at the beginning of an entry
+# 
+# 0.4d
+# - fixed order of disabling statusbars
+# - several attempts at special chars, without any real success
+#   and much more weird new bugs caused by this
+# - setting to specify sort order
+# - reduced timeout values
+# - added awl_hide_data for Geert Hauwaerts ( geert@irssi.org ) :)
+# - make it so the dynamic sub is actually deleted
+# - fix a bug with removing of the last separator
+# - take into consideration parse_special
+# 
+# 0.3b
+# - automatically kill old statusbars
+# - reset on /reload
+# - position/placement settings
+#
+# 0.2
+# - automated retrieval of key bindings (thanks grep.pl authors)
+# - improved removing of statusbars
+# - got rid of status chop
+#
+# 0.1
+# - rewritten to suit my needs
+# - based on chanact 0.5.5
+# }}}
+# vim: se fdm=marker tw=80 :
diff --git a/dotfiles/irssi/scripts/autorun/adv_windowlist.pl b/dotfiles/irssi/scripts/autorun/adv_windowlist.pl
new file mode 120000 (symlink)
index 0000000..d210a5c
--- /dev/null
@@ -0,0 +1 @@
+../adv_windowlist.pl
\ No newline at end of file
diff --git a/dotfiles/irssi/scripts/autorun/hilightwin.pl b/dotfiles/irssi/scripts/autorun/hilightwin.pl
new file mode 120000 (symlink)
index 0000000..fb9568b
--- /dev/null
@@ -0,0 +1 @@
+../hilightwin.pl
\ No newline at end of file
diff --git a/dotfiles/irssi/scripts/autorun/niq.pl b/dotfiles/irssi/scripts/autorun/niq.pl
new file mode 120000 (symlink)
index 0000000..6fbb467
--- /dev/null
@@ -0,0 +1 @@
+../niq.pl
\ No newline at end of file
diff --git a/dotfiles/irssi/scripts/autorun/nm.pl b/dotfiles/irssi/scripts/autorun/nm.pl
new file mode 120000 (symlink)
index 0000000..e486d46
--- /dev/null
@@ -0,0 +1 @@
+../nm.pl
\ No newline at end of file
diff --git a/dotfiles/irssi/scripts/autorun/postpone.pl b/dotfiles/irssi/scripts/autorun/postpone.pl
new file mode 120000 (symlink)
index 0000000..08aff6a
--- /dev/null
@@ -0,0 +1 @@
+../postpone.pl
\ No newline at end of file
diff --git a/dotfiles/irssi/scripts/autorun/screen_away.pl b/dotfiles/irssi/scripts/autorun/screen_away.pl
new file mode 120000 (symlink)
index 0000000..baa7b36
--- /dev/null
@@ -0,0 +1 @@
+../screen_away.pl
\ No newline at end of file
diff --git a/dotfiles/irssi/scripts/autorun/topic-diff.pl b/dotfiles/irssi/scripts/autorun/topic-diff.pl
new file mode 120000 (symlink)
index 0000000..707981e
--- /dev/null
@@ -0,0 +1 @@
+../topic-diff.pl
\ No newline at end of file
diff --git a/dotfiles/irssi/scripts/autorun/trackbar.pl b/dotfiles/irssi/scripts/autorun/trackbar.pl
new file mode 120000 (symlink)
index 0000000..8c5a483
--- /dev/null
@@ -0,0 +1 @@
+../trackbar.pl
\ No newline at end of file
diff --git a/dotfiles/irssi/scripts/autorun/usercount.pl b/dotfiles/irssi/scripts/autorun/usercount.pl
new file mode 120000 (symlink)
index 0000000..1afd2ea
--- /dev/null
@@ -0,0 +1 @@
+../usercount.pl
\ No newline at end of file
diff --git a/dotfiles/irssi/scripts/autorun/xchatnickcolor.pl b/dotfiles/irssi/scripts/autorun/xchatnickcolor.pl
new file mode 120000 (symlink)
index 0000000..e1798b9
--- /dev/null
@@ -0,0 +1 @@
+../xchatnickcolor.pl
\ No newline at end of file
diff --git a/dotfiles/irssi/scripts/beep_beep.pl b/dotfiles/irssi/scripts/beep_beep.pl
new file mode 100644 (file)
index 0000000..139b95a
--- /dev/null
@@ -0,0 +1,51 @@
+#!/usr/bin/irssi
+#
+# irssi beep replace script (tested with irssi v0.8.8.CVS (20030126-1726))
+# (C) 2002-2004 Ge0rG@IRCnet (Georg Lukas <georg@op-co.de>)
+# inspired and tested by Macrotron@IRCnet (macrotron@president.eu.org)
+
+# added beep_flood to irssi settings: beep_cmd will be run not more often
+# then every $beep_flood milliseconds
+
+# fixed memory leak with timeout_add (made irssi waste 80mb and more after a day of IRC)
+# added > /dev/null, thx to Luis Oliveira
+# fixed timeout handling bug, thx to frizop@charter.net
+
+$VERSION = "0.10";
+%IRSSI = (
+    authors    => "Georg Lukas",
+    contact    => "georg\@op-co.de",
+    name       => "beep_beep",
+    description        => "runs arbitrary command instead of system beep, includes flood protection",
+    license    => "Public Domain",
+    url                => "http://op-co.de/irssi/",
+);
+
+use Irssi;
+
+my $might_beep = 1, $to_tag;
+
+sub beep_overflow_timeout() {
+       $might_beep = 1;
+       Irssi::timeout_remove($to_tag);
+}
+
+sub beep_beep() {
+       my $beep_cmd = Irssi::settings_get_str("beep_cmd");
+       if ($beep_cmd) {
+               if ($might_beep) {
+                       my $beep_flood = Irssi::settings_get_int('beep_flood');
+                       $beep_flood = 1000 if $beep_flood < 0;
+                       Irssi::timeout_remove($to_tag);
+                       $to_tag = Irssi::timeout_add($beep_flood, 'beep_overflow_timeout', undef);
+                       system($beep_cmd);
+                       $might_beep = 0;
+               }
+               Irssi::signal_stop();
+       }
+}
+
+Irssi::settings_add_str("lookandfeel", "beep_cmd", "ogg123 --quiet /usr/share/sounds/KDE-Im-Irc-Event.ogg &");
+Irssi::settings_add_int("lookandfeel", "beep_flood", 1000);
+Irssi::signal_add("beep", "beep_beep");
+
diff --git a/dotfiles/irssi/scripts/cap_sasl.pl b/dotfiles/irssi/scripts/cap_sasl.pl
new file mode 100644 (file)
index 0000000..1de3aca
--- /dev/null
@@ -0,0 +1,272 @@
+use strict;
+use Irssi;
+use vars qw($VERSION %IRSSI);
+# $Id$
+
+use MIME::Base64;
+
+$VERSION = "1.1";
+
+%IRSSI = (
+    authors     => 'Michael Tharp and Jilles Tjoelker',
+    contact     => 'gxti@partiallystapled.com',
+    name        => 'cap_sasl.pl',
+    description => 'Implements PLAIN SASL authentication mechanism for use with charybdis ircds, and enables CAP MULTI-PREFIX',
+    license     => 'GNU General Public License',
+    url         => 'http://sasl.charybdis.be/',
+);
+
+my %sasl_auth = ();
+my %mech = ();
+
+sub timeout;
+
+sub server_connected {
+       my $server = shift;
+       $server->send_raw_now("CAP LS");
+}
+
+sub event_cap {
+       my ($server, $args, $nick, $address) = @_;
+       my ($subcmd, $caps, $tosend);
+
+       $tosend = '';
+       if ($args =~ /^\S+ (\S+) :(.*)$/) {
+               $subcmd = uc $1;
+               $caps = ' '.$2.' ';
+               if ($subcmd eq 'LS') {
+                       $tosend .= ' multi-prefix' if $caps =~ / multi-prefix /i;
+                       $tosend .= ' sasl' if $caps =~ / sasl /i && defined($sasl_auth{$server->{tag}});
+                       $tosend =~ s/^ //;
+                       $server->print('', "CLICAP: supported by server:$caps");
+                       if (!$server->{connected}) {
+                               if ($tosend eq '') {
+                                       $server->send_raw_now("CAP END");
+                               } else {
+                                       $server->print('', "CLICAP: requesting: $tosend");
+                                       $server->send_raw_now("CAP REQ :$tosend");
+                               }
+                       }
+                       Irssi::signal_stop();
+               } elsif ($subcmd eq 'ACK') {
+                       $server->print('', "CLICAP: now enabled:$caps");
+                       if ($caps =~ / sasl /i) {
+                               $sasl_auth{$server->{tag}}{buffer} = '';
+                               if($mech{$sasl_auth{$server->{tag}}{mech}}) {
+                                       $server->send_raw_now("AUTHENTICATE " . $sasl_auth{$server->{tag}}{mech});
+                                       Irssi::timeout_add_once(5000, \&timeout, $server->{tag});
+                               }else{
+                                       $server->print('', 'SASL: attempted to start unknown mechanism "' . $sasl_auth{$server->{tag}}{mech} . '"');
+                               }
+                       }
+                       elsif (!$server->{connected}) {
+                               $server->send_raw_now("CAP END");
+                       }
+                       Irssi::signal_stop();
+               } elsif ($subcmd eq 'NAK') {
+                       $server->print('', "CLICAP: refused:$caps");
+                       if (!$server->{connected}) {
+                               $server->send_raw_now("CAP END");
+                       }
+                       Irssi::signal_stop();
+               } elsif ($subcmd eq 'LIST') {
+                       $server->print('', "CLICAP: currently enabled:$caps");
+                       Irssi::signal_stop();
+               }
+       }
+}
+
+sub event_authenticate {
+       my ($server, $args, $nick, $address) = @_;
+       my $sasl = $sasl_auth{$server->{tag}};
+       return unless $sasl && $mech{$sasl->{mech}};
+
+       $sasl->{buffer} .= $args;
+       return if length($args) == 400;
+
+       my $data = $sasl->{buffer} eq '+' ? '' : decode_base64($sasl->{buffer});
+       my $out = $mech{$sasl->{mech}}($sasl, $data);
+       $out = '' unless defined $out;
+       $out = $out eq '' ? '+' : encode_base64($out, '');
+
+       while(length $out >= 400) {
+               my $subout = substr($out, 0, 400, '');
+               $server->send_raw_now("AUTHENTICATE $subout");
+       }
+       if(length $out) {
+               $server->send_raw_now("AUTHENTICATE $out");
+       }else{ # Last piece was exactly 400 bytes, we have to send some padding to indicate we're done
+               $server->send_raw_now("AUTHENTICATE +");
+       }
+
+       $sasl->{buffer} = '';
+       Irssi::signal_stop();
+}
+
+sub event_saslend {
+       my ($server, $args, $nick, $address) = @_;
+
+       my $data = $args;
+       $data =~ s/^\S+ :?//;
+       # need this to see it, ?? -- jilles
+       $server->print('', $data);
+       if (!$server->{connected}) {
+               $server->send_raw_now("CAP END");
+       }
+}
+
+sub timeout {
+       my $tag = shift;
+       my $server = Irssi::server_find_tag($tag);
+       if(!$server->{connected}) {
+               $server->print('', "SASL: authentication timed out");
+               $server->send_raw_now("CAP END");
+       }
+}
+
+sub cmd_sasl {
+       my ($data, $server, $item) = @_;
+
+       if ($data ne '') {
+               Irssi::command_runsub ('sasl', $data, $server, $item);
+       } else {
+               cmd_sasl_show(@_);
+       }
+}
+
+sub cmd_sasl_set {
+       my ($data, $server, $item) = @_;
+
+       if (my($net, $u, $p, $m) = $data =~ /^(\S+) (\S+) (\S+) (\S+)$/) {
+               if($mech{uc $m}) {
+                       $sasl_auth{$net}{user} = $u;
+                       $sasl_auth{$net}{password} = $p;
+                       $sasl_auth{$net}{mech} = uc $m;
+                       Irssi::print("SASL: added $net: [$m] $sasl_auth{$net}{user} *");
+               }else{
+                       Irssi::print("SASL: unknown mechanism $m");
+               }
+       } elsif ($data =~ /^(\S+)$/) {
+               $net = $1;
+               if (defined($sasl_auth{$net})) {
+                       delete $sasl_auth{$net};
+                       Irssi::print("SASL: deleted $net");
+               } else {
+                       Irssi::print("SASL: no entry for $net");
+               }
+       } else {
+               Irssi::print("SASL: usage: /sasl set <net> <user> <password or keyfile> <mechanism>");
+       }
+}
+
+sub cmd_sasl_show {
+       #my ($data, $server, $item) = @_;
+       my $net;
+       my $count = 0;
+
+       foreach $net (keys %sasl_auth) {
+               Irssi::print("SASL: $net: [$sasl_auth{$net}{mech}] $sasl_auth{$net}{user} *");
+               $count++;
+       }
+       Irssi::print("SASL: no networks defined") if !$count;
+}
+
+sub cmd_sasl_save {
+       #my ($data, $server, $item) = @_;
+       my $file = Irssi::get_irssi_dir()."/sasl.auth";
+       open FILE, "> $file" or return;
+       foreach my $net (keys %sasl_auth) {
+               printf FILE ("%s\t%s\t%s\t%s\n", $net, $sasl_auth{$net}{user}, $sasl_auth{$net}{password}, $sasl_auth{$net}{mech});
+       }
+       close FILE;
+       Irssi::print("SASL: auth saved to $file");
+}
+
+sub cmd_sasl_load {
+       #my ($data, $server, $item) = @_;
+       my $file = Irssi::get_irssi_dir()."/sasl.auth";
+
+       open FILE, "< $file" or return;
+       %sasl_auth = ();
+       while (<FILE>) {
+               chomp;
+               my ($net, $u, $p, $m) = split (/\t/, $_, 4);
+               $m ||= "PLAIN";
+               if($mech{uc $m}) {
+                       $sasl_auth{$net}{user} = $u;
+                       $sasl_auth{$net}{password} = $p;
+                       $sasl_auth{$net}{mech} = uc $m;
+               }else{
+                       Irssi::print("SASL: unknown mechanism $m");
+               }
+       }
+       close FILE;
+       Irssi::print("SASL: auth loaded from $file");
+}
+
+sub cmd_sasl_mechanisms {
+       Irssi::print("SASL: mechanisms supported: " . join(" ", keys %mech));
+}
+
+Irssi::signal_add_first('server connected', \&server_connected);
+Irssi::signal_add('event cap', \&event_cap);
+Irssi::signal_add('event authenticate', \&event_authenticate);
+Irssi::signal_add('event 903', 'event_saslend');
+Irssi::signal_add('event 904', 'event_saslend');
+Irssi::signal_add('event 905', 'event_saslend');
+Irssi::signal_add('event 906', 'event_saslend');
+Irssi::signal_add('event 907', 'event_saslend');
+
+Irssi::command_bind('sasl', \&cmd_sasl);
+Irssi::command_bind('sasl load', \&cmd_sasl_load);
+Irssi::command_bind('sasl save', \&cmd_sasl_save);
+Irssi::command_bind('sasl set', \&cmd_sasl_set);
+Irssi::command_bind('sasl show', \&cmd_sasl_show);
+Irssi::command_bind('sasl mechanisms', \&cmd_sasl_mechanisms);
+
+$mech{PLAIN} = sub {
+       my($sasl, $data) = @_;
+       my $u = $sasl->{user};
+       my $p = $sasl->{password};
+
+       join("\0", $u, $u, $p);
+};
+
+eval {
+       use Crypt::OpenSSL::Bignum;
+       use Crypt::DH;
+       use Crypt::Blowfish;
+       use Math::BigInt;
+       sub bin2bi { return Crypt::OpenSSL::Bignum->new_from_bin(shift)->to_decimal } # binary to BigInt
+       sub bi2bin { return Crypt::OpenSSL::Bignum->new_from_decimal((shift)->bstr)->to_bin } # BigInt to binary
+       $mech{'DH-BLOWFISH'} = sub {
+               my($sasl, $data) = @_;
+               my $u = $sasl->{user};
+               my $pass = $sasl->{password};
+
+               # Generate private key and compute secret key
+               my($p, $g, $y) = unpack("(n/a*)3", $data);
+               my $dh = Crypt::DH->new(p => bin2bi($p), g => bin2bi($g));
+               $dh->generate_keys;
+
+               my $secret = bi2bin($dh->compute_secret(bin2bi($y)));
+               my $pubkey = bi2bin($dh->pub_key);
+
+               # Pad the password to the nearest multiple of blocksize and encrypt
+               $pass .= "\0";
+               $pass .= chr(rand(256)) while length($pass) % 8;
+
+               my $cipher = Crypt::Blowfish->new($secret);
+               my $crypted = '';
+               while(length $pass) {
+                       my $clear = substr($pass, 0, 8, '');
+                       $crypted .= $cipher->encrypt($clear);
+               }
+
+               pack("n/a*Z*a*", $pubkey, $u, $crypted);
+       };
+};
+
+cmd_sasl_load();
+
+# vim: ts=4
diff --git a/dotfiles/irssi/scripts/hilightwin.pl b/dotfiles/irssi/scripts/hilightwin.pl
new file mode 100644 (file)
index 0000000..f7af8a7
--- /dev/null
@@ -0,0 +1,57 @@
+#
+# Print hilighted messages & private messages to window named "hilight" for
+# irssi 0.7.99 by Timo Sirainen
+#
+# Modded a tiny bit by znx to stop private messages entering the hilighted
+# window (can be toggled) and to put up a timestamp.
+#
+
+use Irssi;
+use POSIX;
+use vars qw($VERSION %IRSSI); 
+
+$VERSION = "0.02";
+%IRSSI = (
+    authors     => "Timo \'cras\' Sirainen, Mark \'znx\' Sangster",
+    contact     => "tss\@iki.fi, znxster\@gmail.com", 
+    name        => "hilightwin",
+    description => "Print hilighted messages to window named \"hilight\"",
+    license     => "Public Domain",
+    url         => "http://irssi.org/",
+    changed     => "Sun May 25 18:59:57 BST 2008"
+);
+
+sub sig_printtext {
+    my ($dest, $text, $stripped) = @_;
+
+    my $opt = MSGLEVEL_HILIGHT;
+
+    if(Irssi::settings_get_bool('hilightwin_showprivmsg')) {
+        $opt = MSGLEVEL_HILIGHT|MSGLEVEL_MSGS;
+    }
+    
+    if(
+        ($dest->{level} & ($opt)) &&
+        ($dest->{level} & MSGLEVEL_NOHILIGHT) == 0
+    ) {
+        $window = Irssi::window_find_name('hilight');
+        
+        if ($dest->{level} & MSGLEVEL_PUBLIC) {
+            $text = $dest->{target}.": ".$text;
+        }
+        $text = strftime(
+            Irssi::settings_get_str('timestamp_format')." ",
+            localtime
+        ).$text;
+        $window->print($text, MSGLEVEL_NEVER) if ($window);
+    }
+}
+
+$window = Irssi::window_find_name('hilight');
+Irssi::print("Create a window named 'hilight'") if (!$window);
+
+Irssi::settings_add_bool('hilightwin','hilightwin_showprivmsg',1);
+
+Irssi::signal_add('print text', 'sig_printtext');
+
+# vim:set ts=4 sw=4 et:
diff --git a/dotfiles/irssi/scripts/nicklist.pl b/dotfiles/irssi/scripts/nicklist.pl
new file mode 100644 (file)
index 0000000..0dbe4fb
--- /dev/null
@@ -0,0 +1,611 @@
+# for documentation: see http://wouter.coekaerts.be/site/irssi/nicklist
+
+use Irssi;
+use strict;
+use IO::Handle; # for (auto)flush
+use Fcntl; # for sysopen
+use vars qw($VERSION %IRSSI);
+$VERSION = '0.4.6';
+%IRSSI = (
+       authors     => 'Wouter Coekaerts',
+       contact     => 'coekie@irssi.org',
+       name        => 'nicklist',
+       description => 'draws a nicklist to another terminal, or at the right of your irssi in the same terminal',
+       license     => 'GPLv2',
+       url         => 'http://wouter.coekaerts.be/irssi',
+       changed     => '29/06/2004'
+);
+
+sub cmd_help {
+       print ( <<EOF
+Commands:
+NICKLIST HELP
+NICKLIST SCROLL <nr of lines>
+NICKLIST SCREEN
+NICKLIST FIFO
+NICKLIST OFF
+NICKLIST UPDATE
+
+For help see: http://wouter.coekaerts.be/site/irssi/nicklist
+
+in short:
+
+1. FIFO MODE
+- in irssi: /NICKLIST FIFO (only the first time, to create the fifo)
+- in a shell, in a window where you want the nicklist: cat ~/.irssi/nicklistfifo
+- back in irssi:
+    /SET nicklist_heigth <height of nicklist>
+    /SET nicklist_width <width of nicklist>
+    /NICKLIST FIFO
+
+2. SCREEN MODE
+- start irssi inside screen ("screen irssi")
+- /NICKLIST SCREEN
+EOF
+    );
+}
+
+my $prev_lines = 0;                  # number of lines in previous written nicklist
+my $scroll_pos = 0;                  # scrolling position
+my $cursor_line;                     # line the cursor is currently on
+my ($OFF, $SCREEN, $FIFO) = (0,1,2); # modes
+my $mode = $OFF;                     # current mode
+my $need_redraw = 0;                 # nicklist needs redrawing
+my $screen_resizing = 0;             # terminal is being resized
+my $active_channel;                  # (REC)
+
+my @nicklist=();                     # array of hashes, containing the internal nicklist of the active channel
+       # nick => realnick
+       # mode =>
+       my ($MODE_OP, $MODE_HALFOP, $MODE_VOICE, $MODE_NORMAL) = (0,1,2,3);
+       # status =>
+       my ($STATUS_NORMAL, $STATUS_JOINING, $STATUS_PARTING, $STATUS_QUITING, $STATUS_KICKED, $STATUS_SPLIT) = (0,1,2,3,4,5);
+       # text => text to be printed
+       # cmp => text used to compare (sort) nicks
+
+
+# 'cached' settings
+my ($screen_prefix, $irssi_width, @prefix_mode, @prefix_status, $height, $nicklist_width);
+
+sub read_settings {
+       ($screen_prefix = Irssi::settings_get_str('nicklist_screen_prefix')) =~ s/\\e/\033/g;
+
+       ($prefix_mode[$MODE_OP] = Irssi::settings_get_str('nicklist_prefix_mode_op')) =~ s/\\e/\033/g;
+       ($prefix_mode[$MODE_HALFOP] = Irssi::settings_get_str('nicklist_prefix_mode_halfop')) =~ s/\\e/\033/g;
+       ($prefix_mode[$MODE_VOICE] = Irssi::settings_get_str('nicklist_prefix_mode_voice')) =~ s/\\e/\033/g;
+       ($prefix_mode[$MODE_NORMAL] = Irssi::settings_get_str('nicklist_prefix_mode_normal')) =~ s/\\e/\033/g;
+       
+       if ($mode != $SCREEN) {
+               $height = Irssi::settings_get_int('nicklist_height');
+       }
+       my $new_nicklist_width = Irssi::settings_get_int('nicklist_width');
+       if ($new_nicklist_width != $nicklist_width && $mode == $SCREEN) {
+               sig_terminal_resized();
+       }
+       $nicklist_width = $new_nicklist_width;
+}
+
+sub update {
+       read_settings();
+       make_nicklist();
+}
+
+##################
+##### OUTPUT #####
+##################
+
+### off ###
+
+sub cmd_off {
+       if ($mode == $SCREEN) {
+               screen_stop();
+       } elsif ($mode == $FIFO) {
+               fifo_stop();
+       }
+}
+
+### fifo ###
+
+sub cmd_fifo_start {
+       read_settings();
+       my $path = Irssi::settings_get_str('nicklist_fifo_path');
+       unless (-p $path) { # not a pipe
+           if (-e _) { # but a something else
+               die "$0: $path exists and is not a pipe, please remove it\n";
+           } else {
+               require POSIX;
+               POSIX::mkfifo($path, 0666) or die "can\'t mkfifo $path: $!";
+               Irssi::print("Fifo created. Start reading it (\"cat $path\") and try again.");
+               return;
+           }
+       }
+       if (!sysopen(FIFO, $path, O_WRONLY | O_NONBLOCK)) { # or die "can't write $path: $!";
+               Irssi::print("Couldn\'t write to the fifo ($!). Please start reading the fifo (\"cat $path\") and try again.");
+               return;
+       }
+       FIFO->autoflush(1);
+       print FIFO "\033[2J\033[1;1H"; # erase screen & jump to 0,0
+       $cursor_line = 0;
+       if ($mode == $SCREEN) {
+               screen_stop();
+       }
+       $mode = $FIFO;
+       make_nicklist();
+}
+
+sub fifo_stop {
+       close FIFO;
+       $mode = $OFF;
+       Irssi::print("Fifo closed.");
+}
+
+### screen ###
+
+sub cmd_screen_start {
+       if (!defined($ENV{'STY'})) {
+               Irssi::print 'screen not detected, screen mode only works inside screen';
+               return;
+       }
+       read_settings();
+       if ($mode == $SCREEN) {return;}
+       if ($mode == $FIFO) {
+               fifo_stop();
+       }
+       $mode = $SCREEN;
+       Irssi::signal_add_last('gui print text finished', \&sig_gui_print_text_finished);
+       Irssi::signal_add_last('gui page scrolled', \&sig_page_scrolled);
+       Irssi::signal_add('terminal resized', \&sig_terminal_resized);
+       screen_size();
+       make_nicklist();
+}
+
+sub screen_stop {
+       $mode = $OFF;
+       Irssi::signal_remove('gui print text finished', \&sig_gui_print_text_finished);
+       Irssi::signal_remove('gui page scrolled', \&sig_page_scrolled);
+       Irssi::signal_remove('terminal resized', \&sig_terminal_resized);
+       system 'screen -x '.$ENV{'STY'}.' -X fit';
+}
+
+sub screen_size {
+       if ($mode != $SCREEN) {
+               return;
+       }
+       $screen_resizing = 1;
+       # fit screen
+       system 'screen -x '.$ENV{'STY'}.' -X fit';
+       # get size (from perldoc -q size)
+       my ($winsize, $row, $col, $xpixel, $ypixel);
+       eval 'use Term::ReadKey; ($col, $row, $xpixel, $ypixel) = GetTerminalSize';
+       #       require Term::ReadKey 'GetTerminalSize';
+       #       ($col, $row, $xpixel, $ypixel) = Term::ReadKey::GetTerminalSize;
+       #};
+       if ($@) { # no Term::ReadKey, try the ugly way
+               eval {
+                       require 'sys/ioctl.ph';
+                       # without this reloading doesn't work. workaround for some unknown bug
+                       do 'asm/ioctls.ph';
+               };
+               
+               # ugly way not working, let's try something uglier, the dg-hack(tm) (constant for linux only?)
+               if($@) { no strict 'refs'; *TIOCGWINSZ = sub { return 0x5413 } }
+               
+               unless (defined &TIOCGWINSZ) {
+                       die "Term::ReadKey not found, and ioctl 'workaround' failed. Install the Term::ReadKey perl module to use screen mode.\n";
+               }
+               open(TTY, "+</dev/tty") or die "No tty: $!";
+               unless (ioctl(TTY, &TIOCGWINSZ, $winsize='')) {
+                       die "Term::ReadKey not found, and ioctl 'workaround' failed ($!). Install the Term::ReadKey perl module to use screen mode.\n";
+               }
+               close(TTY);
+               ($row, $col, $xpixel, $ypixel) = unpack('S4', $winsize);
+       }
+       
+       # set screen width
+       $irssi_width = $col-$nicklist_width-1;
+       $height = $row-1;
+       
+       # on some recent systems, "screen -X fit; screen -X width -w 50" doesn't work, needs a sleep in between the 2 commands
+       # so we wait a second before setting the width
+       Irssi::timeout_add_once(1000, sub {
+               my ($new_irssi_width) = @_;
+               system 'screen -x '.$ENV{'STY'}.' -X width -w ' . $new_irssi_width;
+               # and then we wait another second for the resizing, and then redraw.
+               Irssi::timeout_add_once(1000,sub {$screen_resizing = 0; redraw()}, []);
+       }, $irssi_width);
+}
+
+sub sig_terminal_resized {
+       if ($screen_resizing) {
+               return;
+       }
+       $screen_resizing = 1;
+       Irssi::timeout_add_once(1000,\&screen_size,[]);
+}
+
+
+### both ###
+
+sub nicklist_write_start {
+       if ($mode == $SCREEN) {
+               print STDERR "\033P\033[s\033\\"; # save cursor
+       }
+}
+
+sub nicklist_write_end {
+       if ($mode == $SCREEN) {
+               print STDERR "\033P\033[u\033\\"; # restore cursor
+       }
+}
+
+sub nicklist_write_line {
+       my ($line, $data) = @_;
+       if ($mode == $SCREEN) {
+               print STDERR "\033P\033[" . ($line+1) . ';'. ($irssi_width+1) .'H'. $screen_prefix . $data . "\033\\";
+       } elsif ($mode == $FIFO) {
+               $data = "\033[m$data"; # reset color
+               if ($line == $cursor_line+1) {
+                       $data = "\n$data"; # next line
+               } elsif ($line == $cursor_line) {
+                       $data = "\033[1G".$data; # back to beginning of line
+               } else {
+                       $data = "\033[".($line+1).";0H".$data; # jump
+               }
+               $cursor_line=$line;
+               print(FIFO $data) or fifo_stop();
+       }
+}
+
+# recalc the text of the nicklist item
+sub calc_text {
+       my ($nick) = @_;
+       my $tmp = $nicklist_width-3;
+       (my $text = $nick->{'nick'}) =~ s/^(.{$tmp})..+$/$1\033[34m~\033[m/;
+       $nick->{'text'} = $prefix_mode[$nick->{'mode'}] . $text . (' ' x ($nicklist_width-length($nick->{'nick'})-1));
+       $nick->{'cmp'} = $nick->{'mode'}.lc($nick->{'nick'});
+}
+
+# redraw the given nick (nr) if it is visible
+sub redraw_nick_nr {
+       my ($nr) = @_;
+       my $line = $nr - $scroll_pos;
+       if ($line >= 0 && $line < $height) {
+               nicklist_write_line($line, $nicklist[$nr]->{'text'});
+       }
+}
+
+# nick was inserted, redraw area if necessary
+sub draw_insert_nick_nr {
+       my ($nr) = @_;
+       my $line = $nr - $scroll_pos;
+       if ($line < 0) { # nick is inserted above visible area
+               $scroll_pos++; # 'scroll' down :)
+       } elsif ($line < $height) { # line is visible
+               if ($mode == $SCREEN) {
+                       need_redraw();
+               } elsif ($mode == $FIFO) {
+                       my $data = "\033[m\033[L". $nicklist[$nr]->{'text'}; # reset color & insert line & write nick
+                       if ($line == $cursor_line) {
+                               $data = "\033[1G".$data; # back to beginning of line
+                       } else {
+                               $data = "\033[".($line+1).";1H".$data; # jump
+                       }
+                       $cursor_line=$line;
+                       print(FIFO $data) or fifo_stop();
+                       if ($prev_lines < $height) {
+                               $prev_lines++; # the nicklist has one line more
+                       }
+               }
+       }
+}
+
+sub draw_remove_nick_nr {
+       my ($nr) = @_;
+       my $line = $nr - $scroll_pos;
+       if ($line < 0) { # nick removed above visible area
+               $scroll_pos--; # 'scroll' up :)
+       } elsif ($line < $height) { # line is visible
+               if ($mode == $SCREEN) {
+                       need_redraw();
+               } elsif ($mode == $FIFO) {
+                       #my $data = "\033[m\033[L[i$line]". $nicklist[$nr]->{'text'}; # reset color & insert line & write nick
+                       my $data = "\033[M"; # delete line
+                       if ($line != $cursor_line) {
+                               $data = "\033[".($line+1)."d".$data; # jump
+                       }
+                       $cursor_line=$line;
+                       print(FIFO $data) or fifo_stop();
+                       if (@nicklist-$scroll_pos >= $height) {
+                               redraw_nick_nr($scroll_pos+$height-1);
+                       }
+               }
+       }
+}
+
+# redraw the whole nicklist
+sub redraw {
+       $need_redraw = 0;
+       #make_nicklist();
+       nicklist_write_start();
+       my $line = 0;
+       ### draw nicklist ###
+       for (my $i=$scroll_pos;$line < $height && $i < @nicklist; $i++) {
+               nicklist_write_line($line++, $nicklist[$i]->{'text'});
+       }
+
+       ### clean up other lines ###
+       my $real_lines = $line;
+       while($line < $prev_lines) {
+               nicklist_write_line($line++,' ' x $nicklist_width);
+       }
+       $prev_lines = $real_lines;
+       nicklist_write_end();
+}
+
+# redraw (with little delay to avoid redrawing to much)
+sub need_redraw {
+       if(!$need_redraw) {
+               $need_redraw = 1;
+               Irssi::timeout_add_once(10,\&redraw,[]);
+       }
+}
+
+sub sig_page_scrolled {
+       $prev_lines = $height; # we'll need to redraw everything if he scrolled up
+       need_redraw;
+}
+
+# redraw (with delay) if the window is visible (only in screen mode)
+sub sig_gui_print_text_finished {
+       if ($need_redraw) { # there's already a redraw 'queued'
+               return;
+       }
+       my $window = @_[0];
+       if ($window->{'refnum'} == Irssi::active_win->{'refnum'} || Irssi::settings_get_str('nicklist_screen_split_windows') eq '*') {
+               need_redraw;
+               return;
+       }
+       foreach my $win (split(/[ ,]/, Irssi::settings_get_str('nicklist_screen_split_windows'))) {
+               if ($window->{'refnum'} == $win || $window->{'name'} eq $win) {
+                       need_redraw;
+                       return;
+               }
+       }
+}
+
+####################
+##### NICKLIST #####
+####################
+
+# returns the position of the given nick(as string) in the (internal) nicklist
+sub find_nick {
+       my ($nick) = @_;
+       for (my $i=0;$i < @nicklist; $i++) {
+               if ($nicklist[$i]->{'nick'} eq $nick) {
+                       return $i;
+               }
+       }
+       return -1;
+}
+
+# find position where nick should be inserted into the list
+sub find_insert_pos {
+       my ($cmp)= @_;
+       for (my $i=0;$i < @nicklist; $i++) {
+               if ($nicklist[$i]->{'cmp'} gt $cmp) {
+                       return $i;
+               }
+       }
+       return scalar(@nicklist); #last
+}
+
+# make the (internal) nicklist (@nicklist)
+sub make_nicklist {
+       @nicklist = ();
+       $scroll_pos = 0;
+
+       ### get & check channel ###
+       my $channel = Irssi::active_win->{active};
+
+       if (!$channel || (ref($channel) ne 'Irssi::Irc::Channel' && ref($channel) ne 'Irssi::Silc::Channel') || $channel->{'type'} ne 'CHANNEL' || ($channel->{chat_type} ne 'SILC' && !$channel->{'names_got'}) ) {
+               $active_channel = undef;
+               # no nicklist
+       } else {
+               $active_channel = $channel;
+               ### make nicklist ###
+               my $thisnick;
+               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()) {
+                       $thisnick = {'nick' => $nick->{'nick'}, 'mode' => ($nick->{'op'}?$MODE_OP:$nick->{'halfop'}?$MODE_HALFOP:$nick->{'voice'}?$MODE_VOICE:$MODE_NORMAL)};
+                       calc_text($thisnick);
+                       push @nicklist, $thisnick;
+               }
+       }
+       need_redraw();
+}
+
+# insert nick(as hash) into nicklist
+# pre: cmp has to be calculated
+sub insert_nick {
+       my ($nick) = @_;
+       my $nr = find_insert_pos($nick->{'cmp'});
+       splice @nicklist, $nr, 0, $nick;
+       draw_insert_nick_nr($nr);
+}
+
+# remove nick(as nr) from nicklist
+sub remove_nick {
+       my ($nr) = @_;
+       splice @nicklist, $nr, 1;
+       draw_remove_nick_nr($nr);
+}
+
+###################
+##### ACTIONS #####
+###################
+
+# scroll the nicklist, arg = number of lines to scroll, positive = down, negative = up
+sub cmd_scroll {
+       if (!$active_channel) { # not a channel active
+               return;
+       }
+       my @nicks=Irssi::active_win->{active}->nicks;
+       my $nick_count = scalar(@nicks)+0;
+       my $channel = Irssi::active_win->{active};
+       if (!$channel || $channel->{type} ne 'CHANNEL' || !$channel->{names_got} || $nick_count <= Irssi::settings_get_int('nicklist_height')) {
+               return;
+       }
+       $scroll_pos += @_[0];
+
+       if ($scroll_pos > $nick_count - $height) {
+               $scroll_pos = $nick_count - $height;
+       }
+       if ($scroll_pos <= 0) {
+               $scroll_pos = 0;
+       }
+       need_redraw();
+}
+
+sub is_active_channel {
+       my ($server,$channel) = @_; # (channel as string)
+       return ($server && $server->{'tag'} eq $active_channel->{'server'}->{'tag'} && $server->channel_find($channel) && $active_channel && $server->channel_find($channel)->{'name'} eq $active_channel->{'name'});
+}
+
+sub sig_channel_wholist { # this is actualy a little late, when the names are received would be better
+       my ($channel) = @_;
+       if (Irssi::active_win->{'active'} && Irssi::active_win->{'active'}->{'name'} eq $channel->{'name'}) { # the channel joined is active
+               make_nicklist
+       }
+}
+
+sub sig_join {
+       my ($server,$channel,$nick,$address) = @_;
+       if (!is_active_channel($server,$channel)) {
+               return;
+       }
+       my $newnick = {'nick' => $nick, 'mode' => $MODE_NORMAL};
+       calc_text($newnick);
+       insert_nick($newnick);
+}
+
+sub sig_kick {
+       my ($server, $channel, $nick, $kicker, $address, $reason) = @_;
+       if (!is_active_channel($server,$channel)) {
+               return;
+       }
+       my $nr = find_nick($nick);
+       if ($nr == -1) {
+               Irssi::print("nicklist warning: $nick was kicked from $channel, but not found in nicklist");
+       } else {
+               remove_nick($nr);
+       }
+}
+
+sub sig_part {
+       my ($server,$channel,$nick,$address, $reason) = @_;
+       if (!is_active_channel($server,$channel)) {
+               return;
+       }
+       my $nr = find_nick($nick);
+       if ($nr == -1) {
+               Irssi::print("nicklist warning: $nick has parted $channel, but was not found in nicklist");
+       } else {
+               remove_nick($nr);
+       }
+
+}
+
+sub sig_quit {
+       my ($server,$nick,$address, $reason) = @_;
+       if ($server->{'tag'} ne $active_channel->{'server'}->{'tag'}) {
+               return;
+       }
+       my $nr = find_nick($nick);
+       if ($nr != -1) {
+               remove_nick($nr);
+       }
+}
+
+sub sig_nick {
+       my ($server, $newnick, $oldnick, $address) = @_;
+       if ($server->{'tag'} ne $active_channel->{'server'}->{'tag'}) {
+               return;
+       }
+       my $nr = find_nick($oldnick);
+       if ($nr != -1) { # if nick was found (nickchange is in current channel)
+               my $nick = $nicklist[$nr];
+               remove_nick($nr);
+               $nick->{'nick'} = $newnick;
+               calc_text($nick);
+               insert_nick($nick);
+       }
+}
+
+sub sig_mode {
+       my ($channel, $nick, $setby, $mode, $type) = @_; # (nick and channel as rec)
+       if ($channel->{'server'}->{'tag'} ne $active_channel->{'server'}->{'tag'} || $channel->{'name'} ne $active_channel->{'name'}) {
+               return;
+       }
+       my $nr = find_nick($nick->{'nick'});
+       if ($nr == -1) {
+               Irssi::print("nicklist warning: $nick->{'nick'} had mode set on $channel->{'name'}, but was not found in nicklist");
+       } else {
+               my $nicklist_item = $nicklist[$nr];
+               remove_nick($nr);
+               $nicklist_item->{'mode'} = ($nick->{'op'}?$MODE_OP:$nick->{'halfop'}?$MODE_HALFOP:$nick->{'voice'}?$MODE_VOICE:$MODE_NORMAL);
+               calc_text($nicklist_item);
+               insert_nick($nicklist_item);
+       }
+}
+
+##### command binds #####
+Irssi::command_bind 'nicklist' => sub {
+    my ( $data, $server, $item ) = @_;
+    $data =~ s/\s+$//g;
+    Irssi::command_runsub ('nicklist', $data, $server, $item ) ;
+};
+Irssi::signal_add_first 'default command nicklist' => sub {
+       # gets triggered if called with unknown subcommand
+       cmd_help();
+};
+Irssi::command_bind('nicklist update',\&update);
+Irssi::command_bind('nicklist help',\&cmd_help);
+Irssi::command_bind('nicklist scroll',\&cmd_scroll);
+Irssi::command_bind('nicklist fifo',\&cmd_fifo_start);
+Irssi::command_bind('nicklist screen',\&cmd_screen_start);
+Irssi::command_bind('nicklist screensize',\&screen_size);
+Irssi::command_bind('nicklist off',\&cmd_off);
+
+##### signals #####
+Irssi::signal_add_last('window item changed', \&make_nicklist);
+Irssi::signal_add_last('window changed', \&make_nicklist);
+Irssi::signal_add_last('channel wholist', \&sig_channel_wholist);
+Irssi::signal_add_first('message join', \&sig_join); # first, to be before ignores
+Irssi::signal_add_first('message part', \&sig_part);
+Irssi::signal_add_first('message kick', \&sig_kick);
+Irssi::signal_add_first('message quit', \&sig_quit);
+Irssi::signal_add_first('message nick', \&sig_nick);
+Irssi::signal_add_first('message own_nick', \&sig_nick);
+Irssi::signal_add_first('nick mode changed', \&sig_mode);
+
+Irssi::signal_add('setup changed', \&read_settings);
+
+##### settings #####
+Irssi::settings_add_str('nicklist', 'nicklist_screen_prefix', '\e[m ');
+Irssi::settings_add_str('nicklist', 'nicklist_prefix_mode_op', '\e[32m@\e[39m');
+Irssi::settings_add_str('nicklist', 'nicklist_prefix_mode_halfop', '\e[34m%\e[39m');
+Irssi::settings_add_str('nicklist', 'nicklist_prefix_mode_voice', '\e[33m+\e[39m');
+Irssi::settings_add_str('nicklist', 'nicklist_prefix_mode_normal', ' ');
+
+Irssi::settings_add_int('nicklist', 'nicklist_width',11);
+Irssi::settings_add_int('nicklist', 'nicklist_height',24);
+Irssi::settings_add_str('nicklist', 'nicklist_fifo_path', Irssi::get_irssi_dir . '/nicklistfifo');
+Irssi::settings_add_str('nicklist', 'nicklist_screen_split_windows', '');
+Irssi::settings_add_str('nicklist', 'nicklist_automode', '');
+
+read_settings();
+if (uc(Irssi::settings_get_str('nicklist_automode')) eq 'SCREEN') {
+       cmd_screen_start();
+} elsif (uc(Irssi::settings_get_str('nicklist_automode')) eq 'FIFO') {
+       cmd_fifo_start();
+}
diff --git a/dotfiles/irssi/scripts/niq.pl b/dotfiles/irssi/scripts/niq.pl
new file mode 100644 (file)
index 0000000..479ffcc
--- /dev/null
@@ -0,0 +1,295 @@
+# BitchX TAB complete style
+# for irssi 0.7.99 by bd@bc-bd.org
+#
+# <tab> signal handling learned from dictcomplete by Timo Sirainen
+#
+# thx go out to fuchs, darix, dg, peder and all on #irssi who helped
+#
+#########
+# USAGE
+###
+# 
+# In a channel window type "ab<tab>" to see a list of nicks starting
+# with "ab".
+# If you now press <tab> again, irssi will default to its own nick
+# completion method.
+# If you enter more characters you can use <tab> again to see a list
+# of the matching nicks, or to complete the nick if there is only
+# one matching.
+#
+# The last completion is saved so if you press "<tab>" with an empty
+# input line, you get the last completed nick.
+#
+# Now there is a statusbar item where you can see the completing
+# nicks instead of in the channel window. There are two ways to 
+# use it:
+#
+#      1) Inside another statusbar
+#
+#              /set niq_show_in_statusbar ON
+#              /statusbar window add -before more niq
+#
+#      2) In an own statusbar
+#
+#              /statusbar niq enable
+#              /statusbar niq add niq
+#              /statusbar niq disable
+#              /set niq_show_in_statusbar ON
+#              /set niq_own_statusbar ON
+#
+#        You can also hide the bar when not completing nicks by using
+#
+#              /set niq_hide_on_inactive ON
+#
+#########
+# OPTIONS
+#########
+#
+# /set niq_show_in_statusbar <ON|OFF>
+#              * ON  : show the completing nicks in a statusbar item
+#              * OFF : show the nicks in the channel window
+#
+# /set niq_own_statusbar <ON|OFF>
+#              * ON  : use an own statusbar for the nicks
+#              * OFF : just use an item
+#
+# /set niq_hide_on_inactive <ON|OFF>
+#              * ON  : hide the own statusbar on inactivity
+#              * OFF : dont hide it
+#
+# /set niq_color_char <ON|OFF>
+#              * ON  : colors the next unlikely character
+#              * OFF : boring no colors
+#
+###
+################
+###
+# Changelog
+#
+# Version 0.5.7
+# - use configured completion_char instead of a colon
+# - removed old, unused code
+# - fixed url
+# - fixed documentation leading to emtpy statusbar
+# - removed warning about a problem with irssi version 0.8.4
+#
+# Version 0.5.6
+# - work around an use problem
+#
+# Version 0.5.5
+# - fixed completion for nicks starting with special chars
+#
+# Version 0.5.4
+# - removed unneeded sort() of colored nicks
+# - moved colored nick generation to where it is needed
+# - the statusbar only worked with colorized nicks (duh!)
+# 
+# Version 0.5.3
+#      - stop nickcompleting if last char is the completion_char
+#        which is in most cases ':'
+#
+# Version 0.5.2
+#      - fixed vanishing statusbar. it wrongly was reset on any
+#        privmsg.
+#
+# Version 0.5.1
+#      - changed statusbar to be off by default since most people 
+#        dont use the latest fixed version.
+#
+# Version 0.5
+#      - added own statusbar option
+#  - added color char option
+#
+# Version 0.4
+#      - added an niq statusbar
+#
+# Version 0.3
+#  - added default to irssi method on <tab><tab>
+#
+# Version 0.2 
+#  - added lastcomp support
+#
+# Version 0.1
+#  - initial release
+###
+################
+
+use Irssi;
+use Irssi::TextUI;
+use strict;
+
+use vars qw($VERSION %IRSSI);
+
+$VERSION="0.5.7";
+%IRSSI = (
+       authors=> 'BC-bd',
+       contact=> 'bd@bc-bd.org',
+       name=> 'niq',
+       description=> 'BitchX like Nickcompletion at line start plus statusbar',
+       license=> 'GPL v2',
+       url=> 'https://bc-bd.org/cgi-bin/gitweb.cgi?p=irssi.git;a=summary',
+);
+
+my($lastword,$lastcomp,$niqString);
+
+$lastcomp = "";
+$lastword = "";
+
+# build our nick with completion_char, add to complist and stop the signal
+sub buildNickAndStop {
+       my ($complist,$nick) = @_;
+       my $push = $nick.Irssi::settings_get_str('completion_char');
+               
+       $lastcomp = $nick;
+       $lastword = "";
+       push (@{$complist}, $push);
+
+       if (Irssi::settings_get_bool('niq_show_in_statusbar') == 1) {
+               drawStatusbar("");
+       }
+
+       Irssi::signal_stop();
+}
+
+# the signal handler
+sub sig_complete {
+       my ($complist, $window, $word, $linestart, $want_space) = @_;
+
+       # still allow channel- #<tab>, /set n<tab>, etc completion.
+       if ($linestart ne "") {
+               return;
+       }
+
+       # also back out if nothing has been entered and lastcomp is ""
+       if ($word eq "") {
+               if ($lastcomp ne "") {
+                       buildNickAndStop($complist,$lastcomp);
+                       return;
+               } else {
+                       return;
+               }
+       }
+       if (rindex($word,Irssi::settings_get_str('completion_char')) == length($word) -1) {
+               chop($word);
+               buildNickAndStop($complist,$word,0);
+               return;
+       }
+
+       my $channel = $window->{active};
+
+       # the completion is ok if this is a channel
+       if ($channel->{type} ne "CHANNEL") 
+       {
+               return;
+       }
+
+       my (@nicks);
+
+       # get the matching nicks but quote this l33t special chars like ^
+       my $shortestNick = 999;
+       my $quoted = quotemeta $word;
+       foreach my $n ($channel->nicks()) {
+               if ($n->{nick} =~ /^$quoted/i && $window->{active_server}->{nick} ne $n->{nick}) {
+                       push(@nicks,$n->{nick});
+                       if (length($n->{nick}) < $shortestNick) {
+                               $shortestNick = length($n->{nick});
+                       }
+               }
+       }
+
+       @nicks = sort(@nicks);
+       
+       # if theres only one nick return it.
+       if (scalar @nicks eq 1)
+       {
+               buildNickAndStop($complist,$nicks[0]);
+       } elsif (scalar @nicks gt 1) {
+               # check if this is <tab> or <tab><tab>
+               if ($lastword eq $word) {
+                       # <tab><tab> so default to the irssi method
+                       sort(@nicks);
+                       for (@nicks) {
+                               $_ .= Irssi::settings_get_str ('completion_char');
+                       }
+
+                       push (@{$complist}, @nicks);
+
+                       # but delete lastword to be ready for the next <tab>
+                       $lastword = "";
+
+                       if (Irssi::settings_get_bool('niq_show_in_statusbar') == 1) {
+                               drawStatusbar("");
+                       }
+
+                       return;
+               } else {
+                       # <tab> only so just print
+               
+                       # build string w/o colored nicks
+                       if (Irssi::settings_get_bool('niq_color_char') == 1) {
+                               $niqString = "";
+                               foreach my $n (@nicks) {
+                                       my $coloredNick = $n;
+                                       $coloredNick =~ s/($quoted)(.)(.*)/$1%_$2%_$3/i;
+                                       $niqString .= "$coloredNick ";
+                               }
+                       } else {
+                               $niqString = join(" ",@nicks);
+                       }
+                       
+                       if (Irssi::settings_get_bool('niq_show_in_statusbar') == 1) {
+                               drawStatusbar($niqString);
+                       } else {
+                               $window->print($niqString);
+                       }
+
+                       Irssi::signal_stop();
+
+                       # remember last word
+                       $lastword = $word;
+
+                       return;
+               }
+       } 
+}
+
+sub emptyBar() {
+       $lastword = "";
+       
+       drawStatusbar("");
+}
+
+sub drawStatusbar() {
+       my ($word) = @_;
+
+       if (Irssi::settings_get_bool('niq_own_statusbar') == 1) {
+               if (Irssi::settings_get_bool('niq_hide_on_inactive') == 1) {
+                       if ($word eq "") {
+                               Irssi::command("statusbar niq disable");
+                       } else {
+                               Irssi::command("statusbar niq enable");
+                       }
+               }
+       }
+
+       $niqString = "{sb $word}";
+       Irssi::statusbar_items_redraw('niq');
+}
+
+sub niqStatusbar() {
+       my ($item, $get_size_only) = @_;
+
+       $item->default_handler($get_size_only, $niqString, undef, 1);
+}
+
+Irssi::signal_add_first('complete word', 'sig_complete');
+Irssi::signal_add_last('window changed', 'emptyBar');
+Irssi::signal_add('message own_public', 'emptyBar');
+
+Irssi::statusbar_item_register('niq', '$0', 'niqStatusbar');
+Irssi::statusbars_recreate_items();
+
+Irssi::settings_add_bool('misc', 'niq_show_in_statusbar', 0);
+Irssi::settings_add_bool('misc', 'niq_own_statusbar', 0);
+Irssi::settings_add_bool('misc', 'niq_hide_on_inactive', 1);
+Irssi::settings_add_bool('misc', 'niq_color_char', 1);
diff --git a/dotfiles/irssi/scripts/nm.pl b/dotfiles/irssi/scripts/nm.pl
new file mode 100644 (file)
index 0000000..b9b2955
--- /dev/null
@@ -0,0 +1,662 @@
+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
+#
+#########
+# 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
+";
+
+#
+###
+################
+###
+#
+# Changelog
+#
+# 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 $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();
+}
+
+# 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});
+}
+
+# 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};
+
+       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});
+               }
+       }
+}
+
+# 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);
+}
+
+# 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);
+       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;
+       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::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');
diff --git a/dotfiles/irssi/scripts/notify.pl b/dotfiles/irssi/scripts/notify.pl
new file mode 100644 (file)
index 0000000..1178307
--- /dev/null
@@ -0,0 +1,77 @@
+##
+## Put me in ~/.irssi/scripts, and then execute the following in irssi:
+##
+##       /load perl
+##       /script load notify
+##
+
+use strict;
+use Irssi;
+use vars qw($VERSION %IRSSI);
+use HTML::Entities;
+
+$VERSION = "0.01";
+%IRSSI = (
+    authors     => 'Luke Macken, Paul W. Frields',
+    contact     => 'lewk@csh.rit.edu, stickster@gmail.com',
+    name        => 'notify.pl',
+    description => 'Use libnotify to alert user to hilighted messages',
+    license     => 'GNU General Public License',
+    url         => 'http://lewk.org/log/code/irssi-notify',
+);
+
+Irssi::settings_add_str('notify', 'notify_icon', 'gtk-dialog-info');
+Irssi::settings_add_str('notify', 'notify_time', '5000');
+
+sub sanitize {
+  my ($text) = @_;
+  encode_entities($text);
+  return $text;
+}
+
+sub notify {
+    my ($server, $summary, $message) = @_;
+
+    # Make the message entity-safe
+    $summary = sanitize($summary);
+    $message = sanitize($message);
+
+    my $cmd = "EXEC - notify-send" .
+       " -i " . Irssi::settings_get_str('notify_icon') .
+       " -t " . Irssi::settings_get_str('notify_time') .
+       " -- '" . $summary . "'" .
+       " '" . $message . "'";
+
+    $server->command($cmd);
+}
+sub print_text_notify {
+    my ($dest, $text, $stripped) = @_;
+    my $server = $dest->{server};
+
+    return if (!$server || !($dest->{level} & MSGLEVEL_HILIGHT));
+    my $sender = $stripped;
+    $sender =~ s/^\<.([^\>]+)\>.+/\1/ ;
+    $stripped =~ s/^\<.[^\>]+\>.// ;
+    my $summary = $dest->{target} . ": " . $sender;
+    notify($server, $summary, $stripped);
+}
+
+sub message_private_notify {
+    my ($server, $msg, $nick, $address) = @_;
+
+    return if (!$server);
+    notify($server, "Private message from ".$nick, $msg);
+}
+
+sub dcc_request_notify {
+    my ($dcc, $sendaddr) = @_;
+    my $server = $dcc->{server};
+
+    return if (!$dcc);
+    notify($server, "DCC ".$dcc->{type}." request", $dcc->{nick});
+}
+
+Irssi::signal_add('print text', 'print_text_notify');
+Irssi::signal_add('message private', 'message_private_notify');
+Irssi::signal_add('dcc request', 'dcc_request_notify');
diff --git a/dotfiles/irssi/scripts/postpone.pl b/dotfiles/irssi/scripts/postpone.pl
new file mode 100644 (file)
index 0000000..72e0a90
--- /dev/null
@@ -0,0 +1,117 @@
+# by Stefan 'tommie' Tomanek <stefan@pico.ruhr.de>
+#
+#
+
+use strict;
+
+use vars qw($VERSION %IRSSI);
+$VERSION = "20030208";
+%IRSSI = (
+    authors     => "Stefan 'tommie' Tomanek",
+    contact     => "stefan\@pico.ruhr.de",
+    name        => "postpone",
+    description => "Postpones messages sent to a splitted user and resends them when the nick rejoins",
+    license     => "GPLv2",
+    changed     => "$VERSION",
+    commands     => "postpone"
+);
+
+use Irssi 20020324;
+use vars qw(%messages);
+
+sub draw_box ($$$$) {
+    my ($title, $text, $footer, $colour) = @_;
+    my $box = '';
+    $box .= '%R,--[%n%9%U'.$title.'%U%9%R]%n'."\n";
+    foreach (split(/\n/, $text)) {
+        $box .= '%R|%n '.$_."\n";
+    }
+    $box .= '%R`--<%n'.$footer.'%R>->%n';
+    $box =~ s/%.//g unless $colour;
+    return $box;
+}
+
+sub show_help() {
+    my $help="Postpone $VERSION
+/postpone help
+    Display this help
+/postpone flush <nick>
+    Flush postponed messages to <nick>
+/postpone list
+    List postponed messages
+";
+    my $text = '';
+    foreach (split(/\n/, $help)) {
+        $_ =~ s/^\/(.*)$/%9\/$1%9/;
+        $text .= $_."\n";
+    }
+    print CLIENTCRAP &draw_box("Postpone", $text, "help", 1);
+}
+
+
+sub event_send_text ($$$) {
+    my ($line, $server, $witem) = @_;
+    return unless ($witem && $witem->{type} eq "CHANNEL");
+    if ($line =~ /^(\w+?): (.*)$/) {
+       my ($target, $msg) = ($1,$2);
+       if ($witem->nick_find($target)) {
+           # Just leave me alone
+           return;
+       } else {
+           $witem->print("%B>>%n %U".$target."%U is not here, message has been postponed: \"".$line."\"", MSGLEVEL_CLIENTCRAP);
+           push @{$messages{$server->{tag}}{$witem->{name}}{$target}}, $line;
+           Irssi::signal_stop();
+       }
+    }
+}
+
+sub event_message_join ($$$$) {
+    my ($server, $channel, $nick, $address) = @_;
+    return unless (defined $messages{$server->{tag}});
+    return unless (defined $messages{$server->{tag}}{$channel});
+    return unless (defined $messages{$server->{tag}}{$channel}{$nick});
+    return unless (scalar(@{$messages{$server->{tag}}{$channel}{$nick}}) > 0);
+    my $chan = $server->channel_find($channel);
+    $chan->print("%B>>%n Sending postponed messages for ".$nick, MSGLEVEL_CLIENTCRAP);
+    while (scalar(@{$messages{$server->{tag}}{$channel}{$nick}}) > 0) {
+       my $msg = pop @{$messages{$server->{tag}}{$channel}{$nick}};
+       $server->command('MSG '.$channel.' '.$msg);
+    }
+    
+}
+
+sub cmd_postpone ($$$) {
+    my ($args, $server, $witem) = @_;
+    my @arg = split(/ /, $args);
+    if (scalar(@arg) < 1) {
+       #foo
+    } elsif ($arg[0] eq 'flush' && defined $arg[1]) {
+       return unless ($witem && $witem->{type} eq "CHANNEL");
+       while (scalar(@{$messages{$server->{tag}}{$witem->{name}}{$arg[1]}}) > 0) {
+           my $msg = pop @{$messages{$server->{tag}}{$witem->{name}}{$arg[1]}};
+           $server->command('MSG '.$witem->{name}.' '.$msg);
+       }
+    } elsif ($arg[0] eq 'list') {
+       my $text;
+       foreach (keys %messages) {
+           $text .= $_."\n";
+           foreach my $channel (keys %{$messages{$_}}) {
+               $text .= " %U".$channel."%U \n";
+               foreach my $nick (sort keys %{$messages{$_}{$channel}}) {
+                   $text .= ' |'.$_."\n" foreach @{$messages{$_}{$channel}{$nick}};
+               }
+           }
+       }
+       print CLIENTCRAP &draw_box('Postpone', $text, 'messages', 1);
+    } elsif ($arg[0] eq 'help') {
+       show_help();
+    }
+}
+
+Irssi::command_bind('postpone', \&cmd_postpone);
+
+Irssi::signal_add('send text', \&event_send_text);
+Irssi::signal_add('message join', \&event_message_join);
+
+print CLIENTCRAP "%B>>%n Postpone ".$VERSION." loaded: /postpone help for help";
+
diff --git a/dotfiles/irssi/scripts/screen_away.pl b/dotfiles/irssi/scripts/screen_away.pl
new file mode 100644 (file)
index 0000000..86e3087
--- /dev/null
@@ -0,0 +1,243 @@
+use Irssi;
+use strict;
+use FileHandle;
+
+use vars qw($VERSION %IRSSI);
+
+$VERSION = "0.9.7.1";
+%IRSSI = (
+    authors     => 'Andreas \'ads\' Scherbaum <ads@wars-nicht.de>',
+    name        => 'screen_away',
+    description => 'set (un)away, if screen is attached/detached',
+    license     => 'GPL v2',
+    url         => 'none',
+);
+
+# screen_away irssi module
+#
+# written by Andreas 'ads' Scherbaum <ads@ufp.de>
+#
+# changes:
+#  07.02.2004 fix error with away mode
+#             thanks to Michael Schiansky for reporting and fixing this one
+#  07.08.2004 new function for changing nick on away
+#  24.08.2004 fixing bug where the away nick was not storedcorrectly
+#             thanks for Harald Wurpts for help debugging this one
+#  17.09.2004 rewrote init part to use $ENV{'STY'}
+#  05.12.2004 add patch for remember away state
+#             thanks to Jilles Tjoelker <jilles@stack.nl>
+#             change "chatnet" to "tag"
+#  18.05.2007 fix '-one' for SILC networks
+#
+#
+# usage:
+#
+# put this script into your autorun directory and/or load it with
+#  /SCRIPT LOAD <name>
+#
+# there are 5 settings available:
+#
+# /set screen_away_active ON/OFF/TOGGLE
+# /set screen_away_repeat <integer>
+# /set screen_away_message <string>
+# /set screen_away_window <string>
+# /set screen_away_nick <string>
+#
+# active means, that you will be only set away/unaway, if this
+#   flag is set, default is ON
+# repeat is the number of seconds, after the script will check the
+#   screen status again, default is 5 seconds
+# message is the away message sent to the server, default: not here ...
+# window is a window number or name, if set, the script will switch
+#   to this window, if it sets you away, default is '1'
+# nick is the new nick, if the script goes away
+#   will only be used it not empty
+#
+# normal you should be able to rename the script to something other
+# than 'screen_away' (as example, if you dont like the name) by simple
+# changing the 'name' parameter in the %IRSSI hash at the top of this script
+
+
+# variables
+my $timer_name = undef;
+my $away_status = 0;
+my %old_nicks = ();
+my %away = ();
+
+# Register formats
+Irssi::theme_register(
+[
+ 'screen_away_crap', 
+ '{line_start}{hilight ' . $IRSSI{'name'} . ':} $0'
+]);
+
+# if we are running
+my $screen_away_used = 0;
+
+# try to find out, if we are running in a screen
+# (see, if $ENV{STY} is set
+if (!defined($ENV{STY})) {
+  # just return, we will never be called again
+  Irssi::printformat(MSGLEVEL_CLIENTCRAP, 'screen_away_crap',
+    "could not open status file for parent process (pid: " . getppid() . "): $!");
+  return;
+}
+
+my ($socket_name, $socket_path);
+
+# search for socket
+# normal we could search the socket file, ... if we know the path
+# but so we have to call one time the screen executable
+# disable locale
+# the quotes around C force perl 5.005_03 to use the shell
+# thanks to Jilles Tjoelker <jilles@stack.nl> for pointing this out
+my $socket = `LC_ALL="C" screen -ls`;
+
+
+
+my $running_in_screen = 0;
+# locale doesnt seems to be an problem (yet)
+if ($socket !~ /^No Sockets found/s) {
+  # ok, should have only one socket
+  $socket_name = $ENV{'STY'};
+  $socket_path = $socket;
+  $socket_path =~ s/^.+\d+ Sockets? in ([^\n]+)\.\n.+$/$1/s;
+  if (length($socket_path) != length($socket)) {
+    # only activate, if string length is different
+    # (to make sure, we really got a dir name)
+    $screen_away_used = 1;
+  } else {
+    Irssi::printformat(MSGLEVEL_CLIENTCRAP, 'screen_away_crap',
+      "error reading screen informations from:");
+    Irssi::printformat(MSGLEVEL_CLIENTCRAP, 'screen_away_crap',
+      "$socket");
+    return;
+  }
+}
+
+# last check
+if ($screen_away_used == 0) {
+  # we will never be called again
+  return;
+}
+
+# build complete socket name
+$socket = $socket_path . "/" . $socket_name;
+
+# register config variables
+Irssi::settings_add_bool('misc', $IRSSI{'name'} . '_active', 1);
+Irssi::settings_add_int('misc', $IRSSI{'name'} . '_repeat', 5);
+Irssi::settings_add_str('misc', $IRSSI{'name'} . '_message', "not here ...");
+Irssi::settings_add_str('misc', $IRSSI{'name'} . '_window', "1");
+Irssi::settings_add_str('misc', $IRSSI{'name'} . '_nick', "");
+
+# init process
+screen_away();
+
+# screen_away()
+#
+# check, set or reset the away status
+#
+# parameter:
+#   none
+# return:
+#   0 (OK)
+sub screen_away {
+  my ($away, @screen, $screen);
+
+  # only run, if activated
+  if (Irssi::settings_get_bool($IRSSI{'name'} . '_active') == 1) {
+    if ($away_status == 0) {
+      # display init message at first time
+      Irssi::printformat(MSGLEVEL_CLIENTCRAP, 'screen_away_crap',
+        "activating $IRSSI{'name'} (interval: " . Irssi::settings_get_int($IRSSI{'name'} . '_repeat') . " seconds)");
+    }
+    # get actual screen status
+    my @screen = stat($socket);
+    # 00100 is the mode for "user has execute permissions", see stat.h
+    if (($screen[2] & 00100) == 0) {
+      # no execute permissions, Detached
+      $away = 1;
+    } else {
+      # execute permissions, Attached
+      $away = 2;
+    }
+
+    # check if status has changed
+    if ($away == 1 and $away_status != 1) {
+      # set away
+      if (length(Irssi::settings_get_str($IRSSI{'name'} . '_window')) > 0) {
+        # if length of window is greater then 0, make this window active
+        Irssi::command('window goto ' . Irssi::settings_get_str($IRSSI{'name'} . '_window'));
+      }
+      Irssi::printformat(MSGLEVEL_CLIENTCRAP, 'screen_away_crap',
+        "Set away");
+      my $message = Irssi::settings_get_str($IRSSI{'name'} . '_message');
+      if (length($message) == 0) {
+        # we have to set a message or we wouldnt go away
+        $message = "not here ...";
+      }
+      my ($server);
+      foreach $server (Irssi::servers()) {
+        if (!$server->{usermode_away}) {
+          # user isnt yet away
+          $away{$server->{'tag'}} = 0;
+          $server->command("AWAY " . (($server->{chat_type} ne 'SILC') ? "-one " : "") . "$message") if (!$server->{usermode_away});
+          if (length(Irssi::settings_get_str($IRSSI{'name'} . '_nick')) > 0) {
+            # only change, if actual nick isnt already the away nick
+            if (Irssi::settings_get_str($IRSSI{'name'} . '_nick') ne $server->{nick}) {
+              # keep old nick
+              $old_nicks{$server->{'tag'}} = $server->{nick};
+              # set new nick
+              $server->command("NICK " . Irssi::settings_get_str($IRSSI{'name'} . '_nick'));
+            }
+          }
+        } else {
+          # user is already away, remember this
+          $away{$server->{'tag'}} = 1;
+        }
+      }
+      $away_status = $away;
+    } elsif ($away == 2 and $away_status != 2) {
+      # unset away
+      Irssi::printformat(MSGLEVEL_CLIENTCRAP, 'screen_away_crap',
+        "Reset away");
+      my ($server);
+      foreach $server (Irssi::servers()) {
+        if ($away{$server->{'tag'}} == 1) {
+          # user was already away, dont reset away
+          $away{$server->{'tag'}} = 0;
+          next;
+        }
+        $server->command("AWAY" . (($server->{chat_type} ne 'SILC') ? " -one" : "")) if ($server->{usermode_away});
+        if (defined($old_nicks{$server->{'tag'}}) and length($old_nicks{$server->{'tag'}}) > 0) {
+          # set old nick
+          $server->command("NICK " . $old_nicks{$server->{'tag'}});
+          $old_nicks{$server->{'tag'}} = "";
+        }
+      }
+      $away_status = $away;
+    }
+  }
+  # but everytimes install a new timer
+  register_screen_away_timer();
+  return 0;
+}
+
+# register_screen_away_timer()
+#
+# remove old timer and install a new one
+#
+# parameter:
+#   none
+# return:
+#   none
+sub register_screen_away_timer {
+  if (defined($timer_name)) {
+    # remove old timer, if defined
+    Irssi::timeout_remove($timer_name);
+  }
+  # add new timer with new timeout (maybe the timeout has been changed)
+  $timer_name = Irssi::timeout_add(Irssi::settings_get_int($IRSSI{'name'} . '_repeat') * 1000, 'screen_away', '');
+}
+
diff --git a/dotfiles/irssi/scripts/scriptassist.pl b/dotfiles/irssi/scripts/scriptassist.pl
new file mode 100644 (file)
index 0000000..8b63139
--- /dev/null
@@ -0,0 +1,1160 @@
+# by Stefan "tommie" Tomanek
+#
+# scriptassist.pl
+
+
+use strict;
+
+use vars qw($VERSION %IRSSI);
+$VERSION = '2003020803';
+%IRSSI = (
+    authors     => 'Stefan \'tommie\' Tomanek',
+    contact     => 'stefan@pico.ruhr.de',
+    name        => 'scriptassist',
+    description => 'keeps your scripts on the cutting edge',
+    license     => 'GPLv2',
+    url         => 'http://irssi.org/scripts/',
+    changed     => $VERSION,
+    modules     => 'Data::Dumper LWP::UserAgent (GnuPG)',
+    commands   => "scriptassist"
+);
+
+use vars qw($forked %remote_db $have_gpg);
+
+use Irssi 20020324;
+use Data::Dumper;
+use LWP::UserAgent;
+use POSIX;
+
+# GnuPG is not always needed
+use vars qw($have_gpg @complist);
+$have_gpg = 0;
+eval "use GnuPG qw(:algo :trust);";
+$have_gpg = 1 if not ($@);
+
+sub show_help() {
+    my $help = "scriptassist $VERSION
+/scriptassist check
+    Check all loaded scripts for new available versions
+/scriptassist update <script|all>
+    Update the selected or all script to the newest version
+/scriptassist search <query>
+    Search the script database
+/scriptassist info <scripts>
+    Display information about <scripts>
+/scriptassist ratings <scripts>
+    Retrieve the average ratings of the the scripts
+/scriptassist top <num>
+    Retrieve the first <num> top rated scripts
+/scriptassist new <num>
+    Display the newest <num> scripts
+/scriptassist rate <script> <stars>
+    Rate the script with a number of stars ranging from 0-5
+/scriptassist contact <script>
+    Write an email to the author of the script
+    (Requires OpenURL)
+/scriptassist cpan <module>
+    Visit CPAN to look for missing Perl modules
+    (Requires OpenURL)
+/scriptassist install <script>
+    Retrieve and load the script
+/scriptassist autorun <script>
+    Toggles automatic loading of <script>
+";  
+    my $text='';
+    foreach (split(/\n/, $help)) {
+        $_ =~ s/^\/(.*)$/%9\/$1%9/;
+        $text .= $_."\n";
+    }
+    print CLIENTCRAP &draw_box("ScriptAssist", $text, "scriptassist help", 1);
+    #theme_box("ScriptAssist", $text, "scriptassist help", 1);
+}
+
+sub theme_box ($$$$) {
+    my ($title, $text, $footer, $colour) = @_;
+    Irssi::printformat(MSGLEVEL_CLIENTCRAP, 'box_header', $title);
+    foreach (split(/\n/, $text)) {
+       Irssi::printformat(MSGLEVEL_CLIENTCRAP, 'box_inside', $_);
+    }
+    Irssi::printformat(MSGLEVEL_CLIENTCRAP, 'box_footer', $footer);
+}
+
+sub draw_box ($$$$) {
+    my ($title, $text, $footer, $colour) = @_;
+    my $box = '';
+    $box .= '%R,--[%n%9%U'.$title.'%U%9%R]%n'."\n";
+    foreach (split(/\n/, $text)) {
+        $box .= '%R|%n '.$_."\n";
+    }                                                                               $box .= '%R`--<%n'.$footer.'%R>->%n';
+    $box =~ s/%.//g unless $colour;
+    return $box;
+}
+
+sub call_openurl ($) {
+    my ($url) = @_;
+    no strict "refs";
+    # check for a loaded openurl
+    if (defined %{ "Irssi::Script::openurl::" }) {
+        &{ "Irssi::Script::openurl::launch_url" }($url);
+    } else {
+        print CLIENTCRAP "%R>>%n Please install openurl.pl";
+    }
+    use strict;
+}
+
+sub bg_do ($) {
+    my ($func) = @_; 
+    my ($rh, $wh);
+    pipe($rh, $wh);
+    if ($forked) {
+       print CLIENTCRAP "%R>>%n Please wait until your earlier request has been finished.";
+       return;
+    }
+    my $pid = fork();
+    $forked = 1;
+    if ($pid > 0) {
+       print CLIENTCRAP "%R>>%n Please wait...";
+        close $wh;
+        Irssi::pidwait_add($pid);
+        my $pipetag;
+        my @args = ($rh, \$pipetag, $func);
+        $pipetag = Irssi::input_add(fileno($rh), INPUT_READ, \&pipe_input, \@args);
+    } else {
+       eval {
+           my @items = split(/ /, $func);
+           my %result;
+           my $ts1 = $remote_db{timestamp};
+           my $xml = get_scripts();
+           my $ts2 = $remote_db{timestamp};
+           if (not($ts1 eq $ts2) && Irssi::settings_get_bool('scriptassist_cache_sources')) {
+               $result{db} = $remote_db{db};
+               $result{timestamp} = $remote_db{timestamp};
+           }
+           if ($items[0] eq 'check') {
+               $result{data}{check} = check_scripts($xml);
+           } elsif ($items[0] eq 'update') {
+               shift(@items);
+               $result{data}{update} = update_scripts(\@items, $xml);
+           } elsif ($items[0] eq 'search') {
+               shift(@items);
+               #$result{data}{search}{-foo} = 0;
+               foreach (@items) {
+                   $result{data}{search}{$_} = search_scripts($_, $xml);
+               }
+           } elsif ($items[0] eq 'install') {
+               shift(@items);
+               $result{data}{install} = install_scripts(\@items, $xml);
+           } elsif ($items[0] eq 'debug') {
+               shift(@items);
+               $result{data}{debug} = debug_scripts(\@items);
+           } elsif ($items[0] eq 'ratings') {
+               shift(@items);
+               @items = @{ loaded_scripts() } if $items[0] eq "all";
+               #$result{data}{rating}{-foo} = 1;
+               my %ratings = %{ get_ratings(\@items, '') };
+               foreach (keys %ratings) {
+                   $result{data}{rating}{$_}{rating} = $ratings{$_}->[0];
+                   $result{data}{rating}{$_}{votes} = $ratings{$_}->[1];
+               }
+           } elsif ($items[0] eq 'rate') {
+               #$result{data}{rate}{-foo} = 1;
+               $result{data}{rate}{$items[1]} = rate_script($items[1], $items[2]);
+           } elsif ($items[0] eq 'info') {
+               shift(@items);
+               $result{data}{info} = script_info(\@items);
+           } elsif ($items[0] eq 'echo') {
+               $result{data}{echo} = 1;
+           } elsif ($items[0] eq 'top') {
+               my %ratings = %{ get_ratings([], $items[1]) };
+               foreach (keys %ratings) {
+                    $result{data}{rating}{$_}{rating} = $ratings{$_}->[0];
+                    $result{data}{rating}{$_}{votes} = $ratings{$_}->[1];
+                }
+           } elsif ($items[0] eq 'new') {
+               my $new = get_new($items[1]);
+               $result{data}{new} = $new;
+           } elsif ($items[0] eq 'unknown') {
+               my $cmd = $items[1];
+               $result{data}{unknown}{$cmd} = get_unknown($cmd, $xml);
+           }
+           my $dumper = Data::Dumper->new([\%result]);
+           $dumper->Purity(1)->Deepcopy(1)->Indent(0);
+           my $data = $dumper->Dump;
+           print($wh $data);
+       };
+       close($wh);
+       POSIX::_exit(1);
+    }
+}
+
+sub get_unknown ($$) {
+    my ($cmd, $db) = @_;
+    foreach (keys %$db) {
+       next unless defined $db->{$_}{commands};
+       foreach my $item (split / /, $db->{$_}{commands}) {
+           return { $_ => $db->{$_} } if ($item =~ /^$cmd$/i);
+       }
+    }
+    return undef;
+}
+
+sub script_info ($) {
+    my ($scripts) = @_;
+    no strict "refs";
+    my %result;
+    my $xml = get_scripts();
+    foreach (@{$scripts}) {
+       next unless (defined $xml->{$_.".pl"} || (defined %{ 'Irssi::Script::'.$_.'::' } && defined %{ 'Irssi::Script::'.$_.'::IRSSI' }));
+       $result{$_}{version} = get_remote_version($_, $xml);
+       my @headers = ('authors', 'contact', 'description', 'license', 'source');
+       foreach my $entry (@headers) {
+           $result{$_}{$entry} = ${ 'Irssi::Script::'.$_.'::IRSSI' }{$entry};
+           if (defined $xml->{$_.".pl"}{$entry}) {
+               $result{$_}{$entry} = $xml->{$_.".pl"}{$entry};
+           }
+       }
+       if ($xml->{$_.".pl"}{signature_available}) {
+           $result{$_}{signature_available} = 1;
+       }
+       if (defined $xml->{$_.".pl"}{modules}) {
+           my $modules = $xml->{$_.".pl"}{modules};
+           #$result{$_}{modules}{-foo} = 1;
+           foreach my $mod (split(/ /, $modules)) {
+               my $opt = ($mod =~ /\((.*)\)/)? 1 : 0;
+               $mod = $1 if $1;
+               $result{$_}{modules}{$mod}{optional} = $opt;
+               $result{$_}{modules}{$mod}{installed} = module_exist($mod);
+           }
+       } elsif (defined ${ 'Irssi::Script::'.$_.'::IRSSI' }{modules}) {
+           my $modules = ${ 'Irssi::Script::'.$_.'::IRSSI' }{modules};
+           foreach my $mod (split(/ /, $modules)) {
+               my $opt = ($mod =~ /\((.*)\)/)? 1 : 0;
+               $mod = $1 if $1;
+               $result{$_}{modules}{$mod}{optional} = $opt;
+               $result{$_}{modules}{$mod}{installed} = module_exist($mod);
+           }
+       }
+       if (defined $xml->{$_.".pl"}{depends}) {
+           my $depends = $xml->{$_.".pl"}{depends};
+           foreach my $dep (split(/ /, $depends)) {
+               $result{$_}{depends}{$dep}{installed} = 1; #(defined ${ 'Irssi::Script::'.$dep }); 
+           }
+       }
+    }
+    return \%result;
+}
+
+sub rate_script ($$) {
+    my ($script, $stars) = @_;
+    my $ua = LWP::UserAgent->new(env_proxy=>1, keep_alive=>1, timeout=>30);
+    $ua->agent('ScriptAssist/'.$VERSION);
+    my $request = HTTP::Request->new('GET', 'http://ratings.irssi.de/irssirate.pl?&stars='.$stars.'&mode=rate&script='.$script);
+    my $response = $ua->request($request);
+    unless ($response->is_success() && $response->content() =~ /You already rated this script/) {
+       return 1;
+    } else {
+       return 0;
+    }
+}
+
+sub get_ratings ($$) {
+    my ($scripts, $limit) = @_;
+    my $ua = LWP::UserAgent->new(env_proxy=>1, keep_alive=>1, timeout=>30);
+    $ua->agent('ScriptAssist/'.$VERSION);
+    my $script = join(',', @{$scripts});
+    my $request = HTTP::Request->new('GET', 'http://ratings.irssi.de/irssirate.pl?script='.$script.'&sort=rating&limit='.$limit);
+    my $response = $ua->request($request);
+    my %result;
+    if ($response->is_success()) {
+       foreach (split /\n/, $response->content()) {
+           if (/<tr><td><a href=".*?">(.*?)<\/a>/) {
+               my $entry = $1;
+               if (/"><\/td><td>([0-9.]+)<\/td><td>(.*?)<\/td><td>/) {
+                   $result{$entry} = [$1, $2];
+               }
+           }
+       }
+    }
+    return \%result;
+}
+
+sub get_new ($) {
+    my ($num) = @_;
+    my $result;
+    my $xml = get_scripts();
+    foreach (sort {$xml->{$b}{last_modified} cmp $xml->{$a}{last_modified}} keys %$xml) {
+       my %entry = %{ $xml->{$_} };
+       $result->{$_} = \%entry;
+       $num--;
+       last unless $num;
+    }
+    return $result;
+}
+sub module_exist ($) {
+    my ($module) = @_;
+    $module =~ s/::/\//g;
+    foreach (@INC) {
+       return 1 if (-e $_."/".$module.".pm");
+    }
+    return 0;
+}
+
+sub debug_scripts ($) {
+    my ($scripts) = @_;
+    my %result;
+    foreach (@{$scripts}) {
+       my $xml = get_scripts();
+       if (defined $xml->{$_.".pl"}{modules}) {
+           my $modules = $xml->{$_.".pl"}{modules};
+           foreach my $mod (split(/ /, $modules)) {
+                my $opt = ($mod =~ /\((.*)\)/)? 1 : 0;
+                $mod = $1 if $1;
+                $result{$_}{$mod}{optional} = $opt;
+                $result{$_}{$mod}{installed} = module_exist($mod);
+           }
+       }
+    }
+    return(\%result);
+}
+
+sub install_scripts ($$) {
+    my ($scripts, $xml) = @_;
+    my %success;
+    #$success{-foo} = 1;
+    my $dir = Irssi::get_irssi_dir()."/scripts/";
+    foreach (@{$scripts}) {
+       if (get_local_version($_) && (-e $dir.$_.".pl")) {
+           $success{$_}{installed} = -2;
+       } else {
+           $success{$_} = download_script($_, $xml);
+       }
+    }
+    return \%success;
+}
+
+sub update_scripts ($$) {
+    my ($list, $database) = @_;
+    $list = loaded_scripts() if ($list->[0] eq "all" || scalar(@$list) == 0);
+    my %status;
+    #$status{-foo} = 1;
+    foreach (@{$list}) {
+       my $local = get_local_version($_);
+       my $remote = get_remote_version($_, $database);
+       next if $local eq '' || $remote eq '';
+       if (compare_versions($local, $remote) eq "older") {
+           $status{$_} = download_script($_, $database);
+       } else {
+           $status{$_}{installed} = -2;
+       }
+       $status{$_}{remote} = $remote;
+       $status{$_}{local} = $local;
+    }
+    return \%status;
+}
+
+sub search_scripts ($$) {
+    my ($query, $database) = @_;
+    my %result;
+    #$result{-foo} = " ";
+    foreach (sort keys %{$database}) {
+       my %entry = %{$database->{$_}};
+       my $string = $_." ";
+       $string .= $entry{description} if defined $entry{description};
+       if ($string =~ /$query/i) {
+           my $name = $_;
+           $name =~ s/\.pl$//;
+           if (defined $entry{description}) {
+               $result{$name}{desc} = $entry{description};
+           } else {
+               $result{$name}{desc} = "";
+           }
+           if (defined $entry{authors}) {
+               $result{$name}{authors} = $entry{authors};
+           } else {
+               $result{$name}{authors} = "";
+           }
+           if (get_local_version($name)) {
+               $result{$name}{installed} = 1;
+           } else {
+               $result{$name}{installed} = 0;
+           }
+       }
+    }
+    return \%result;
+}
+
+sub pipe_input {
+    my ($rh, $pipetag) = @{$_[0]};
+    my @lines = <$rh>;
+    close($rh);
+    Irssi::input_remove($$pipetag);
+    $forked = 0;
+    my $text = join("", @lines);
+    unless ($text) {
+       print CLIENTCRAP "%R<<%n Something weird happend";
+       return();
+    }
+    no strict "vars";
+    my $incoming = eval("$text");
+    if ($incoming->{db} && $incoming->{timestamp}) {
+       $remote_db{db} = $incoming->{db};
+       $remote_db{timestamp} = $incoming->{timestamp};
+    }
+    unless (defined $incoming->{data}) {
+       print CLIENTCRAP "%R<<%n Something weird happend";
+       return;
+    }
+    my %result = %{ $incoming->{data} };
+    @complist = ();
+    if (defined $result{new}) {
+       print_new($result{new});
+       push @complist, $_ foreach keys %{ $result{new} };
+    }
+    if (defined $result{check}) {
+       print_check(%{$result{check}});
+       push @complist, $_ foreach keys %{ $result{check} };
+    }
+    if (defined $result{update}) {
+       print_update(%{ $result{update} });
+       push @complist, $_ foreach keys %{ $result{update} };
+    }
+    if (defined $result{search}) {
+       foreach (keys %{$result{search}}) {
+           print_search($_, %{$result{search}{$_}});
+           push @complist, keys(%{$result{search}{$_}});
+       }
+    }
+    if (defined $result{install}) {
+       print_install(%{ $result{install} });
+       push @complist, $_ foreach keys %{ $result{install} };
+    }
+    if (defined $result{debug}) {
+       print_debug(%{ $result{debug} });
+    }
+    if (defined $result{rating}) {
+       print_ratings(%{ $result{rating} });
+       push @complist, $_ foreach keys %{ $result{rating} };
+    }
+    if (defined $result{rate}) {
+       print_rate(%{ $result{rate} });
+    }
+    if (defined $result{info}) {
+       print_info(%{ $result{info} });
+    }
+    if (defined $result{echo}) {
+       Irssi::print "ECHO";
+    }
+    if ($result{unknown}) {
+        print_unknown($result{unknown});
+    }
+
+}
+
+sub print_unknown ($) {
+    my ($data) = @_;
+    foreach my $cmd (keys %$data) {
+       print CLIENTCRAP "%R<<%n No script provides '/$cmd'" unless $data->{$cmd};
+       foreach (keys %{ $data->{$cmd} }) {
+           my $text .= "The command '/".$cmd."' is provided by the script '".$data->{$cmd}{$_}{name}."'.\n";
+           $text .= "This script is currently not installed on your system.\n";
+           $text .= "If you want to install the script, enter\n";
+           my ($name) = /(.*?)\.pl$/;
+           $text .= "  %U/script install ".$name."%U ";
+           my $output = draw_box("ScriptAssist", $text, "'".$_."' missing", 1);
+           print CLIENTCRAP $output;
+       }
+    }
+}
+
+sub check_autorun ($) {
+    my ($script) = @_;
+    my $dir = Irssi::get_irssi_dir()."/scripts/";
+    if (-e $dir."/autorun/".$script.".pl") {
+       if (readlink($dir."/autorun/".$script.".pl") eq "../".$script.".pl") {
+           return 1;
+       }
+    }
+    return 0;
+}
+
+sub array2table {
+    my (@array) = @_;
+    my @width;
+    foreach my $line (@array) {
+        for (0..scalar(@$line)-1) {
+            my $l = $line->[$_];
+            $l =~ s/%[^%]//g;
+            $l =~ s/%%/%/g;
+            $width[$_] = length($l) if $width[$_]<length($l);
+        }
+    }   
+    my $text;
+    foreach my $line (@array) {
+        for (0..scalar(@$line)-1) {
+            my $l = $line->[$_];
+            $text .= $line->[$_];
+            $l =~ s/%[^%]//g;
+            $l =~ s/%%/%/g;
+            $text .= " "x($width[$_]-length($l)+1) unless ($_ == scalar(@$line)-1);
+        }
+        $text .= "\n";
+    }
+    return $text;
+}
+
+
+sub print_info (%) {
+    my (%data) = @_;
+    my $line;
+    foreach my $script (sort keys(%data)) {
+       my ($local, $autorun);
+       if (get_local_version($script)) {
+           $line .= "%go%n ";
+           $local = get_local_version($script);
+       } else {
+           $line .= "%ro%n ";
+           $local = undef;
+       }
+       if (defined $local || check_autorun($script)) {
+           $autorun = "no";
+           $autorun = "yes" if check_autorun($script);
+       } else {
+           $autorun = undef;
+       }
+       $line .= "%9".$script."%9\n";
+       $line .= "  Version    : ".$data{$script}{version}."\n";
+       $line .= "  Source     : ".$data{$script}{source}."\n";
+       $line .= "  Installed  : ".$local."\n" if defined $local;
+       $line .= "  Autorun    : ".$autorun."\n" if defined $autorun;
+       $line .= "  Authors    : ".$data{$script}{authors};
+       $line .= " %Go-m signed%n" if $data{$script}{signature_available};
+       $line .= "\n";
+       $line .= "  Contact    : ".$data{$script}{contact}."\n";
+       $line .= "  Description: ".$data{$script}{description}."\n";
+       $line .= "\n" if $data{$script}{modules};
+       $line .= "  Needed Perl modules:\n" if $data{$script}{modules};
+
+        foreach (sort keys %{$data{$script}{modules}}) {
+            if ( $data{$script}{modules}{$_}{installed} == 1 ) {
+                $line .= "  %g->%n ".$_." (found)";
+            } else {
+                $line .= "  %r->%n ".$_." (not found)";
+            }
+           $line .= " <optional>" if $data{$script}{modules}{$_}{optional};
+            $line .= "\n";
+        }
+       #$line .= "  Needed Irssi scripts:\n";
+       $line .= "  Needed Irssi Scripts:\n" if $data{$script}{depends};
+       foreach (sort keys %{$data{$script}{depends}}) {
+           if ( $data{$script}{depends}{$_}{installed} == 1 ) {
+               $line .= "  %g->%n ".$_." (loaded)";
+           } else {
+               $line .= "  %r->%n ".$_." (not loaded)";
+           }
+           #$line .= " <optional>" if $data{$script}{depends}{$_}{optional};
+           $line .= "\n";
+       }
+    }
+    print CLIENTCRAP draw_box('ScriptAssist', $line, 'info', 1) ;
+}
+
+sub print_rate (%) {
+    my (%data) = @_;
+    my $line;
+    foreach my $script (sort keys(%data)) {
+       if ($data{$script}) {
+            $line .= "%go%n %9".$script."%9 has been rated";
+        } else {
+            $line .= "%ro%n %9".$script."%9 : Already rated this script";
+        }
+    }
+    print CLIENTCRAP draw_box('ScriptAssist', $line, 'rating', 1) ;
+}
+
+sub print_ratings (%) {
+    my (%data) = @_;
+    my @table;
+    foreach my $script (sort {$data{$b}{rating}<=>$data{$a}{rating}} keys(%data)) {
+       my @line;
+       if (get_local_version($script)) {
+           push @line, "%go%n";
+       } else {
+           push @line, "%yo%n";
+       }
+        push @line, "%9".$script."%9";
+       push @line, $data{$script}{rating};
+       push @line, "[".$data{$script}{votes}." votes]";
+       push @table, \@line;
+    }
+    print CLIENTCRAP draw_box('ScriptAssist', array2table(@table), 'ratings', 1) ;
+}
+
+sub print_new ($) {
+    my ($list) = @_;
+    my @table;
+    foreach (sort {$list->{$b}{last_modified} cmp $list->{$a}{last_modified}} keys %$list) {
+       my @line;
+       my ($name) = /^(.*?)\.pl$/;
+        if (get_local_version($name)) {
+            push @line, "%go%n";
+        } else {
+            push @line, "%yo%n";
+        }
+       push @line, "%9".$name."%9";
+       push @line, $list->{$_}{last_modified};
+       push @table, \@line;
+    }
+    print CLIENTCRAP draw_box('ScriptAssist', array2table(@table), 'new scripts', 1) ;
+}
+
+sub print_debug (%) {
+    my (%data) = @_;
+    my $line;
+    foreach my $script (sort keys %data) {
+       $line .= "%ro%n %9".$script."%9 failed to load\n";
+       $line .= "  Make sure you have the following perl modules installed:\n";
+       foreach (sort keys %{$data{$script}}) {
+           if ( $data{$script}{$_}{installed} == 1 ) {
+               $line .= "  %g->%n ".$_." (found)";
+           } else {
+               $line .= "  %r->%n ".$_." (not found)\n";
+               $line .= "     [This module is optional]\n" if $data{$script}{$_}{optional};
+               $line .= "     [Try /scriptassist cpan ".$_."]";
+           }
+           $line .= "\n";
+       }
+       print CLIENTCRAP draw_box('ScriptAssist', $line, 'debug', 1) ;
+    }
+}
+
+sub load_script ($) {
+    my ($script) = @_;
+    Irssi::command('script load '.$script);
+}
+
+sub print_install (%) {
+    my (%data) = @_;
+    my $text;
+    my ($crashed, @installed);
+    foreach my $script (sort keys %data) {
+       my $line;
+       if ($data{$script}{installed} == 1) {
+           my $hacked;
+           if ($have_gpg && Irssi::settings_get_bool('scriptassist_use_gpg')) {
+               if ($data{$script}{signed} >= 0) {
+                   load_script($script) unless (lc($script) eq lc($IRSSI{name}));
+               } else {
+                   $hacked = 1;
+               }
+           } else {
+               load_script($script) unless (lc($script) eq lc($IRSSI{name}));
+           }
+           if (get_local_version($script) && not lc($script) eq lc($IRSSI{name})) {
+               $line .= "%go%n %9".$script."%9 installed\n";
+               push @installed, $script;
+           } elsif (lc($script) eq lc($IRSSI{name})) {
+               $line .= "%yo%n %9".$script."%9 installed, please reload manually\n";
+           } else {
+               $line .= "%Ro%n %9".$script."%9 fetched, but unable to load\n";
+               $crashed .= $script." " unless $hacked;
+           }
+           if ($have_gpg && Irssi::settings_get_bool('scriptassist_use_gpg')) {
+               foreach (split /\n/, check_sig($data{$script})) {
+                   $line .= "  ".$_."\n";
+               }
+           }
+       } elsif ($data{$script}{installed} == -2) {
+           $line .= "%ro%n %9".$script."%9 already loaded, please try \"update\"\n";
+       } elsif ($data{$script}{installed} <= 0) {
+           $line .= "%ro%n %9".$script."%9 not installed\n";
+           foreach (split /\n/, check_sig($data{$script})) {
+               $line .= "  ".$_."\n";
+           }
+       } else {
+           $line .= "%Ro%n %9".$script."%9 not found on server\n";
+       }
+       $text .= $line;
+    }
+    # Inspect crashed scripts
+    bg_do("debug ".$crashed) if $crashed;
+    print CLIENTCRAP draw_box('ScriptAssist', $text, 'install', 1);
+    list_sbitems(\@installed);
+}
+
+sub list_sbitems ($) {
+    my ($scripts) = @_;
+    my $text;
+    foreach (@$scripts) {
+       no strict 'refs';
+       next unless defined %{ "Irssi::Script::${_}::" };
+       next unless defined %{ "Irssi::Script::${_}::IRSSI" };
+       my %header = %{ "Irssi::Script::${_}::IRSSI" };
+       next unless $header{sbitems};
+       $text .= '%9"'.$_.'"%9 provides the following statusbar item(s):'."\n";
+       $text .= '  ->'.$_."\n" foreach (split / /, $header{sbitems});
+    }
+    return unless $text;
+    $text .= "\n";
+    $text .= "Enter '/statusbar window add <item>' to add an item.";
+    print CLIENTCRAP draw_box('ScriptAssist', $text, 'sbitems', 1);
+}
+
+sub check_sig ($) {
+    my ($sig) = @_;
+    my $line;
+    my %trust = ( -1 => 'undefined',
+                   0 => 'never',
+                  1 => 'marginal',
+                  2 => 'fully',
+                  3 => 'ultimate'
+                );
+    if ($sig->{signed} == 1) {
+       $line .= "Signature found from ".$sig->{sig}{user}."\n";
+       $line .= "Timestamp  : ".$sig->{sig}{date}."\n";
+       $line .= "Fingerprint: ".$sig->{sig}{fingerprint}."\n";
+       $line .= "KeyID      : ".$sig->{sig}{keyid}."\n";
+       $line .= "Trust      : ".$trust{$sig->{sig}{trust}}."\n";
+    } elsif ($sig->{signed} == -1) {
+       $line .= "%1Warning, unable to verify signature%n\n";
+    } elsif ($sig->{signed} == 0) {
+       $line .= "%1No signature found%n\n" unless Irssi::settings_get_bool('scriptassist_install_unsigned_scripts');
+    }
+    return $line;
+}
+
+sub print_search ($%) {
+    my ($query, %data) = @_;
+    my $text;
+    foreach (sort keys %data) {
+       my $line;
+       $line .= "%go%n" if $data{$_}{installed};
+       $line .= "%yo%n" if not $data{$_}{installed};
+       $line .= " %9".$_."%9 ";
+       $line .= $data{$_}{desc};
+       $line =~ s/($query)/%U$1%U/gi;
+       $line .= ' ('.$data{$_}{authors}.')';
+       $text .= $line." \n";
+    }
+    print CLIENTCRAP draw_box('ScriptAssist', $text, 'search: '.$query, 1) ;
+}
+
+sub print_update (%) { 
+    my (%data) = @_;
+    my $text;
+    my @table;
+    my $verbose = Irssi::settings_get_bool('scriptassist_update_verbose');
+    foreach (sort keys %data) {
+       my $signed = 0;
+       if ($data{$_}{installed} == 1) {
+           my $local = $data{$_}{local};
+           my $remote = $data{$_}{remote};
+           push @table, ['%yo%n', '%9'.$_.'%9', 'upgraded ('.$local.'->'.$remote.')'];
+           foreach (split /\n/, check_sig($data{$_})) {
+               push @table, ['', '', $_];
+           }
+           if (lc($_) eq lc($IRSSI{name})) {
+               push @table, ['', '', "%R%9Please reload manually%9%n"];
+           } else {
+               load_script($_);
+           }
+       } elsif ($data{$_}{installed} == 0 || $data{$_}{installed} == -1) {
+           push @table, ['%yo%n', '%9'.$_.'%9', 'not upgraded'];
+            foreach (split /\n/, check_sig($data{$_})) {
+               push @table, ['', '', $_];
+            } 
+       } elsif ($data{$_}{installed} == -2 && $verbose) {
+           my $local = $data{$_}{local};
+           push @table, ['%go%n', '%9'.$_.'%9', 'already at the latest version ('.$local.')'];
+       }
+    }
+    $text = array2table(@table);
+    print CLIENTCRAP draw_box('ScriptAssist', $text, 'update', 1) ;
+}
+
+sub contact_author ($) {
+    my ($script) = @_;
+    no strict 'refs';
+    return unless defined %{ "Irssi::Script::${script}::" };
+    my %header = %{ "Irssi::Script::${script}::IRSSI" };
+    if (defined $header{contact}) {
+       my @ads = split(/ |,/, $header{contact});
+       my $address = $ads[0];
+       $address .= '?subject='.$script;
+       $address .= '_'.get_local_version($script) if defined get_local_version($script);
+       call_openurl($address);
+    }
+}
+
+sub get_scripts {
+    my $ua = LWP::UserAgent->new(env_proxy=>1, keep_alive=>1, timeout=>30);
+    $ua->agent('ScriptAssist/'.$VERSION);
+    $ua->env_proxy();
+    my @mirrors = split(/ /, Irssi::settings_get_str('scriptassist_script_sources'));
+    my %sites_db;
+    my $fetched = 0;
+    my @sources;
+    foreach my $site (@mirrors) {
+       my $request = HTTP::Request->new('GET', $site);
+       if ($remote_db{timestamp}) {
+           $request->if_modified_since($remote_db{timestamp});
+       }
+       my $response = $ua->request($request);
+       next unless $response->is_success;
+       $fetched = 1;
+       my $data = $response->content();
+       my ($src, $type);
+       if ($site =~ /(.*\/).+\.(.+)/) {
+           $src = $1;
+           $type = $2;
+       }
+       push @sources, $src;
+       #my @header = ('name', 'contact', 'authors', 'description', 'version', 'modules', 'last_modified');
+       if ($type eq 'dmp') {
+           no strict 'vars';
+           my $new_db = eval "$data";
+           foreach (keys %$new_db) {
+               if (defined $sites_db{script}{$_}) {
+                   my $old = $sites_db{$_}{version};
+                   my $new = $new_db->{$_}{version};
+                   next if (compare_versions($old, $new) eq 'newer');
+               }
+               #foreach my $key (@header) {
+               foreach my $key (keys %{ $new_db->{$_} }) {
+                   next unless defined $new_db->{$_}{$key};
+                   $sites_db{$_}{$key} = $new_db->{$_}{$key};
+               }
+               $sites_db{$_}{source} = $src;
+           }
+       } else {
+           ## FIXME Panic?!
+       }
+       
+    }
+    if ($fetched) {
+       # Clean database
+       foreach (keys %{$remote_db{db}}) {
+           foreach my $site (@sources) {
+               if ($remote_db{db}{$_}{source} eq $site) {
+                   delete $remote_db{db}{$_};
+                   last;
+               }
+           }
+       }
+       $remote_db{db}{$_} = $sites_db{$_} foreach (keys %sites_db);
+       $remote_db{timestamp} = time();
+    }
+    return $remote_db{db};
+}
+
+sub get_remote_version ($$) {
+    my ($script, $database) = @_;
+    return $database->{$script.".pl"}{version};
+}
+
+sub get_local_version ($) {
+    my ($script) = @_;
+    no strict 'refs';
+    return unless defined %{ "Irssi::Script::${script}::" };
+    my $version = ${ "Irssi::Script::${script}::VERSION" };
+    return $version;
+}
+
+sub compare_versions ($$) {
+    my ($ver1, $ver2) = @_;
+    my @ver1 = split /\./, $ver1;
+    my @ver2 = split /\./, $ver2;
+    #if (scalar(@ver2) != scalar(@ver1)) {
+    #    return 0;
+    #}       
+    my $cmp = 0;
+    ### Special thanks to Clemens Heidinger
+    $cmp ||= $ver1[$_] <=> $ver2[$_] || $ver1[$_] cmp $ver2[$_] for 0..scalar(@ver2);
+    return 'newer' if $cmp == 1;
+    return 'older' if $cmp == -1;
+    return 'equal';
+}
+
+sub loaded_scripts {
+    no strict 'refs';
+    my @modules;
+    foreach (sort grep(s/::$//, keys %Irssi::Script::)) {
+        #my $name    = ${ "Irssi::Script::${_}::IRSSI" }{name};
+        #my $version = ${ "Irssi::Script::${_}::VERSION" };
+       push @modules, $_;# if $name && $version;
+    }
+    return \@modules;
+
+}
+
+sub check_scripts {
+    my ($data) = @_;
+    my %versions;
+    #$versions{-foo} = 1;
+    foreach (@{loaded_scripts()}) {
+        my $remote = get_remote_version($_, $data);
+        my $local =  get_local_version($_);
+       my $state;
+       if ($local && $remote) {
+           $state = compare_versions($local, $remote);
+       } elsif ($local) {
+           $state = 'noversion';
+           $remote = '/';
+       } else {
+           $state = 'noheader';
+           $local = '/';
+           $remote = '/';
+       }
+       if ($state) {
+           $versions{$_}{state} = $state;
+           $versions{$_}{remote} = $remote;
+           $versions{$_}{local} = $local;
+       }
+    }
+    return \%versions;
+}
+
+sub download_script ($$) {
+    my ($script, $xml) = @_;
+    my %result;
+    my $site = $xml->{$script.".pl"}{source};
+    $result{installed} = 0;
+    $result{signed} = 0;
+    my $dir = Irssi::get_irssi_dir();
+    my $ua = LWP::UserAgent->new(env_proxy => 1,keep_alive => 1,timeout => 30);
+    $ua->agent('ScriptAssist/'.$VERSION);
+    my $request = HTTP::Request->new('GET', $site.'/scripts/'.$script.'.pl');
+    my $response = $ua->request($request);
+    if ($response->is_success()) {
+       my $file = $response->content();
+       mkdir $dir.'/scripts/' unless (-e $dir.'/scripts/');
+       local *F;
+       open(F, '>'.$dir.'/scripts/'.$script.'.pl.new');
+       print F $file;
+       close(F);
+       if ($have_gpg && Irssi::settings_get_bool('scriptassist_use_gpg')) {
+           my $ua2 = LWP::UserAgent->new(env_proxy => 1,keep_alive => 1,timeout => 30);
+           $ua->agent('ScriptAssist/'.$VERSION);
+           my $request2 = HTTP::Request->new('GET', $site.'/signatures/'.$script.'.pl.asc');
+           my $response2 = $ua->request($request2);
+           if ($response2->is_success()) {
+               local *S;
+               my $sig_dir = $dir.'/scripts/signatures/';
+               mkdir $sig_dir unless (-e $sig_dir);
+               open(S, '>'.$sig_dir.$script.'.pl.asc');
+               my $file2 = $response2->content();
+               print S $file2;
+               close(S);
+               my $sig;
+               foreach (1..2) {
+                   # FIXME gpg needs two rounds to load the key
+                   my $gpg = new GnuPG();
+                   eval {
+                       $sig = $gpg->verify( file => $dir.'/scripts/'.$script.'.pl.new', signature => $sig_dir.$script.'.pl.asc' );
+                   };
+               }
+               if (defined $sig->{user}) {
+                   $result{installed} = 1;
+                   $result{signed} = 1;
+                   $result{sig}{$_} = $sig->{$_} foreach (keys %{$sig});
+               } else {
+                   # Signature broken?
+                   $result{installed} = 0;
+                   $result{signed} = -1;
+               }
+           } else {
+               $result{signed} = 0;
+               $result{installed} = -1;
+               $result{installed} = 1 if Irssi::settings_get_bool('scriptassist_install_unsigned_scripts');
+           }
+       } else {
+           $result{signed} = 0;
+           $result{installed} = -1;
+           $result{installed} = 1 if Irssi::settings_get_bool('scriptassist_install_unsigned_scripts');
+       }
+    }
+    if ($result{installed}) {
+       my $old_dir = "$dir/scripts/old/";
+       mkdir $old_dir unless (-e $old_dir);
+       rename "$dir/scripts/$script.pl", "$old_dir/$script.pl.old" if -e "$dir/scripts/$script.pl";
+       rename "$dir/scripts/$script.pl.new", "$dir/scripts/$script.pl";
+    }
+    return \%result;
+}
+
+sub print_check (%) {
+    my (%data) = @_;
+    my $text;
+    my @table;
+    foreach (sort keys %data) {
+       my $state = $data{$_}{state};
+       my $remote = $data{$_}{remote};
+       my $local = $data{$_}{local};
+       if (Irssi::settings_get_bool('scriptassist_check_verbose')) {
+           push @table, ['%go%n', '%9'.$_.'%9', 'Up to date. ('.$local.')'] if $state eq 'equal';
+       }
+       push @table, ['%mo%n', '%9'.$_.'%9', "No version information available on network."] if $state eq "noversion";
+       push @table, ['%mo%n', '%9'.$_.'%9', 'No header in script.'] if $state eq "noheader";
+       push @table, ['%bo%n', '%9'.$_.'%9', "Your version is newer (".$local."->".$remote.")"] if $state eq "newer";
+       push @table, ['%ro%n', '%9'.$_.'%9', "A new version is available (".$local."->".$remote.")"] if $state eq "older";;
+    }
+    $text = array2table(@table);
+    print CLIENTCRAP draw_box('ScriptAssist', $text, 'check', 1) ;
+}
+
+sub toggle_autorun ($) {
+    my ($script) = @_;
+    my $dir = Irssi::get_irssi_dir()."/scripts/";
+    mkdir $dir."autorun/" unless (-e $dir."autorun/");
+    return unless (-e $dir.$script.".pl");
+    if (check_autorun($script)) {
+       if (readlink($dir."/autorun/".$script.".pl") eq "../".$script.".pl") {
+           if (unlink($dir."/autorun/".$script.".pl")) {
+               print CLIENTCRAP "%R>>%n Autorun of ".$script." disabled";
+           } else {
+               print CLIENTCRAP "%R>>%n Unable to delete link";
+           }
+       } else {
+           print CLIENTCRAP "%R>>%n ".$dir."/autorun/".$script.".pl is not a correct link";
+       }
+    } else {
+       symlink("../".$script.".pl", $dir."/autorun/".$script.".pl");
+       print CLIENTCRAP "%R>>%n Autorun of ".$script." enabled";
+    }
+}
+
+sub sig_script_error ($$) {
+    my ($script, $msg) = @_;
+    return unless Irssi::settings_get_bool('scriptassist_catch_script_errors');
+    if ($msg =~ /Can't locate (.*?)\.pm in \@INC \(\@INC contains:(.*?) at/) {
+        my $module = $1;
+        $module =~ s/\//::/g;
+       missing_module($module);
+    }
+}
+
+sub missing_module ($$) {
+    my ($module) = @_;
+    my $text;
+    $text .= "The perl module %9".$module."%9 is missing on your system.\n";
+    $text .= "Please ask your administrator about it.\n";
+    $text .= "You can also check CPAN via '/scriptassist cpan ".$module."'.\n";
+    print CLIENTCRAP &draw_box('ScriptAssist', $text, $module, 1);
+}
+
+sub cmd_scripassist ($$$) {
+    my ($arg, $server, $witem) = @_;
+    my @args = split(/ /, $arg);
+    if ($args[0] eq 'help' || $args[0] eq '-h') {
+       show_help();
+    } elsif ($args[0] eq 'check') {
+       bg_do("check");
+    } elsif ($args[0] eq 'update') {
+       shift @args;
+       bg_do("update ".join(' ', @args));
+    } elsif ($args[0] eq 'search' && defined $args[1]) {
+       shift @args;
+       bg_do("search ".join(" ", @args));
+    } elsif ($args[0] eq 'install' && defined $args[1]) {
+       shift @args;
+       bg_do("install ".join(' ', @args));
+    } elsif ($args[0] eq 'contact' && defined $args[1]) {
+       contact_author($args[1]);
+    } elsif ($args[0] eq 'ratings' && defined $args[1]) {
+       shift @args;
+       bg_do("ratings ".join(' ', @args));
+    } elsif ($args[0] eq 'rate' && defined $args[1] && defined $args[2]) {
+       shift @args;
+       bg_do("rate ".join(' ', @args)) if ($args[2] >= 0 && $args[2] < 6);
+    } elsif ($args[0] eq 'info' && defined $args[1]) {
+       shift @args;
+       bg_do("info ".join(' ', @args));
+    } elsif ($args[0] eq 'echo') {
+       bg_do("echo");
+    } elsif ($args[0] eq 'top') {
+       my $number = defined $args[1] ? $args[1] : 10;
+       bg_do("top ".$number);
+    } elsif ($args[0] eq 'cpan' && defined $args[1]) {
+       call_openurl('http://search.cpan.org/search?mode=module&query='.$args[1]);
+    } elsif ($args[0] eq 'autorun' && defined $args[1]) {
+       toggle_autorun($args[1]);
+    } elsif ($args[0] eq 'new') {
+       my $number = defined $args[1] ? $args[1] : 5;
+       bg_do("new ".$number);
+    }
+}
+
+sub sig_command_script_load ($$$) {
+    my ($script, $server, $witem) = @_;
+    no strict;
+    $script = $2 if $script =~ /(.*\/)?(.*?)\.pl$/;
+    if (defined %{ "Irssi::Script::${script}::" }) {
+       if (defined &{ "Irssi::Script::${script}::pre_unload" }) {
+           print CLIENTCRAP "%R>>%n Triggering pre_unload function of $script...";
+           &{ "Irssi::Script::${script}::pre_unload" }();
+       }
+    }
+}
+
+sub sig_default_command ($$) {
+    my ($cmd, $server) = @_;
+    return unless Irssi::settings_get_bool("scriptassist_check_unknown_commands");
+    bg_do('unknown '.$cmd);
+}
+
+sub sig_complete ($$$$$) {
+    my ($list, $window, $word, $linestart, $want_space) = @_;
+    return unless $linestart =~ /^.script(assist)? (install|rate|ratings|update|check|contact|info|autorun)/;
+    my @newlist;
+    my $str = $word;
+    foreach (@complist) {
+       if ($_ =~ /^(\Q$str\E.*)?$/) {
+           push @newlist, $_;
+       }
+    }
+    foreach (@{loaded_scripts()}) {
+       push @newlist, $_ if /^(\Q$str\E.*)?$/;
+    }
+    $want_space = 0;
+    push @$list, $_ foreach @newlist;
+    Irssi::signal_stop();
+}
+
+
+Irssi::settings_add_str($IRSSI{name}, 'scriptassist_script_sources', 'http://www.irssi.org/scripts/scripts.dmp');
+Irssi::settings_add_bool($IRSSI{name}, 'scriptassist_cache_sources', 1);
+Irssi::settings_add_bool($IRSSI{name}, 'scriptassist_update_verbose', 1);
+Irssi::settings_add_bool($IRSSI{name}, 'scriptassist_check_verbose', 1);
+Irssi::settings_add_bool($IRSSI{name}, 'scriptassist_catch_script_errors', 1);
+
+Irssi::settings_add_bool($IRSSI{name}, 'scriptassist_install_unsigned_scripts', 1);
+Irssi::settings_add_bool($IRSSI{name}, 'scriptassist_use_gpg', 1);
+Irssi::settings_add_bool($IRSSI{name}, 'scriptassist_integrate', 1);
+Irssi::settings_add_bool($IRSSI{name}, 'scriptassist_check_unknown_commands', 1);
+
+Irssi::signal_add_first("default command", \&sig_default_command);
+Irssi::signal_add_first('complete word', \&sig_complete);
+Irssi::signal_add_first('command script load', \&sig_command_script_load);
+Irssi::signal_add_first('command script unload', \&sig_command_script_load);
+
+if (defined &Irssi::signal_register) {
+    Irssi::signal_register({ 'script error' => [ 'Irssi::Script', 'string' ] });
+    Irssi::signal_add_last('script error', \&sig_script_error);
+}
+
+Irssi::command_bind('scriptassist', \&cmd_scripassist);
+
+Irssi::theme_register(['box_header', '%R,--[%n$*%R]%n',
+'box_inside', '%R|%n $*',
+'box_footer', '%R`--<%n$*%R>->%n',
+]);
+
+foreach my $cmd ( ( 'check', 'install', 'update', 'contact', 'search', '-h', 'help', 'ratings', 'rate', 'info', 'echo', 'top', 'cpan', 'autorun', 'new') ) {
+    Irssi::command_bind('scriptassist '.$cmd => sub {
+                       cmd_scripassist("$cmd ".$_[0], $_[1], $_[2]); });
+    if (Irssi::settings_get_bool('scriptassist_integrate')) {
+       Irssi::command_bind('script '.$cmd => sub {
+                           cmd_scripassist("$cmd ".$_[0], $_[1], $_[2]); });
+    }
+}
+
+print CLIENTCRAP '%B>>%n '.$IRSSI{name}.' '.$VERSION.' loaded: /scriptassist help for help';
diff --git a/dotfiles/irssi/scripts/topic-diff.pl b/dotfiles/irssi/scripts/topic-diff.pl
new file mode 100644 (file)
index 0000000..6082c81
--- /dev/null
@@ -0,0 +1,86 @@
+use strict;
+use vars qw($VERSION %IRSSI);
+
+use Irssi;
+
+$VERSION = '1.00';
+%IRSSI = (
+    authors     => 'Pascal Hakim',
+    contact     => 'pasc@redellipse.net',
+    name        => 'topic-diff',
+    description => 'This script shows you changes in the topic. ',
+    license     => 'GPL'
+);
+
+my %topics;
+
+sub new_channel {
+    my ($channel) = @_;
+    $topics{$channel->{server}->{tag}."_".$channel->{name}} = $channel->{topic};
+}
+
+sub new_topic {
+    my ($server, $channel, $topic, $user, $real) = @_;
+    my $i;
+    my $diff;
+    my $i = 0;
+    my $j = 0;
+    my $k = 0;
+    
+#    $server->print ($channel, $server->{tag});
+
+    if ($topics{$server->{tag}."_".$channel}) {
+       $topics{$server->{tag}."_".$channel} =~ s/^ +| +$//g;
+       $topic =~ s/^ +| +$//g;
+       my @original = split /\s*\|\s*|\s+-\s+/, $topics{$server->{tag}."_".$channel};
+       my @modified = split /\s*\|\s*|\s+-\s+/, $topic;
+       
+       
+      outer: while( $i <= $#original) {
+         if ($j <= $#modified && $original[$i] eq $modified[$j]) {
+             $modified[$j] = '';
+             $i += 1;
+             $j += 1;
+             next;
+             
+         }  else {
+             # First two don't match, check the rest of the list
+             for ($k = $j ; $k <= $#modified; $k++) {
+                 if ($modified[$k] eq $original[$i])
+                 {       
+                     $modified[$k] = '';
+                     $i += 1;
+                     next outer;
+                 }
+             }
+             $diff = ($diff ? $diff." | " : "").$original[$i];
+             $i += 1;
+         }
+      }
+       
+       
+       if ($diff ne '') { $server->print ($channel, "Topic: -: ".$diff);}
+       
+       $diff = join " | ", (grep {$_ ne ''} @modified);
+
+       if ($diff ne '') { $server->print ($channel, "Topic: +: ".$diff);}
+       
+    }
+    $topics{$server->{tag}."_".$channel} = $topic;
+
+}
+
+
+# Start by reading all the channels currently opened, and recording their topic
+
+my @channels = Irssi::channels () ;
+
+foreach my $channel (@channels) {
+       $topics{$channel->{server}->{tag}."_".$channel->{name}} = $channel->{topic};
+}
+
+# Topic has changed
+Irssi::signal_add 'message topic' => \& new_topic;
+
+# We've joined a new channel
+Irssi::signal_add 'channel joined' => \& new_channel;
diff --git a/dotfiles/irssi/scripts/trackbar.pl b/dotfiles/irssi/scripts/trackbar.pl
new file mode 100644 (file)
index 0000000..a34fed1
--- /dev/null
@@ -0,0 +1,228 @@
+# trackbar.pl
+# 
+# This little script will do just one thing: it will draw a line each time you
+# switch away from a window. This way, you always know just upto where you've
+# been reading that window :) It also removes the previous drawn line, so you
+# don't see double lines.
+#
+# Usage: 
+#
+#     The script works right out of the box, but if you want you can change
+#     the working by /set'ing the following variables:
+#
+#     trackbar_string       The characters to repeat to draw the bar
+#     trackbar_style        The style for the bar, %r is red for example
+#                           See formats.txt that came with irssi
+#
+#     /mark is a command that will redraw the line at the bottom.  However!  This
+#     requires irssi version after 20021228.  otherwise you'll get the error
+#     redraw: unknown command, and your screen is all goofed up :)
+#
+#     /upgrade & buf.pl notice: This version tries to remove the trackbars before 
+#     the upgrade is done, so buf.pl does not restore them, as they are not removeable
+#     afterwards by trackbar.  Unfortiounatly, to make this work, trackbar and buf.pl
+#     need to be loaded in a specific order.  Please experiment to see which order works
+#     for you (strangely, it differs from configuration to configuration, something I will
+#     try to fix in a next version) 
+#
+# Authors:
+#   - Main maintainer & author: Peter 'kinlo' Leurs
+#   - Many thanks to Timo 'cras' Sirainen for placing me on my way
+#   - on-upgrade-remove-line patch by Uwe Dudenhoeffer
+#
+# Version history:
+#  1.4: - Changed our's by my's so the irssi script header is valid
+#       - Removed utf-8 support.  In theory, the script should work w/o any
+#         problems for utf-8, just set trackbar_string to a valid utf-8 character
+#         and everything *should* work.  However, this script is being plagued by
+#         irssi internal bugs.  The function Irssi::settings_get_str does NOT handle
+#         unicode strings properly, hence you will notice problems when setting the bar
+#         to a unicode char.  For changing your bar to utf-8 symbols, read the line sub.
+#  1.3: - Upgrade now removes the trackbars. 
+#       - Some code cleanups, other defaults
+#       - /mark sets the line to the bottom
+#  1.2: - Support for utf-8
+#       - How the bar looks can now be configured with trackbar_string 
+#         and trackbar_style
+#  1.1: - Fixed bug when closing window
+#  1.0: - Initial release
+#
+#
+#  Call for help!
+#
+#  There is a trackbar version 2.0 that properly handles resizes and immediate config change
+#  activation.  However, there is/are some bug(s) in irssi's main buffer/window code that causes
+#  irssi to 'forget' lines, which is ofcourse completly unaccepteable.  I haven't found the time
+#  nor do I know the irssi's internals enough to find and fix this bug, if you want to help, please
+#  contact me, I'll give you a copy of the 2.0 version that will immediatly show you the problems.
+#
+# Known bugs:
+#  - if you /clear a window, it will be uncleared when returning to the window
+#  - UTF-8 characters in the trackbar_string doesnt work.  This is an irssi bug.
+#  - if you resize your irssi (in xterm or so) the bar is not resized 
+#  - changing the trackbar style is only visible after returning to a window
+#  however, changing style/resize takes in effect after you left the window.
+#
+# Whishlist/todo:
+#  - instead of drawing a line, just invert timestamp or something, 
+#    to save a line (but I don't think this is possible with current irssi)
+#  - some pageup keybinding possibility, to scroll up upto the trackbar
+#  - <@coekie> kinlo: if i switch to another window, in another split window, i 
+#              want the trackbar to go down in the previouswindow in  that splitwindow :)
+#  - < bob_2> anyway to clear the line once the window is read?
+#  - < elho> kinlo: wishlist item: a string that gets prepended to the repeating pattern
+#  - < elho> an option to still have the timestamp in front of the bar
+#  - < elho> oh and an option to not draw it in the status window :P
+#
+# BTW: when you have feature requests, mailing a patch that works is the fastest way
+# to get it added :p
+
+use strict;
+use 5.6.1;
+use Irssi;
+use Irssi::TextUI;
+use POSIX qw(strftime);
+
+my $VERSION = "1.4";
+
+my %IRSSI = (
+    authors     => "Peter 'kinlo' Leurs",
+    contact     => "peter\@pfoe.be",
+    name        => "trackbar",
+    description => "Shows a bar where you've last read a window",
+    license     => "GPLv2",
+    url         => "http://www.pfoe.be/~peter/trackbar/",
+    changed     => "Thu Feb 20 16:18:08 2003",
+);
+
+my %config;
+
+Irssi::theme_register([
+    'trackbar_loaded', '%R>>%n %_Scriptinfo:%_ Loaded $0 version $1 by $2.',
+    'trackbar_wrong_style', '%R>>%n %_Trackbar:%_ I detected a malformed %_trackbar_style%_, the default setting has been restored.',
+    'trackbar_wrong_string', '%R>>%n %_Trackbar:%_ I detected a malformed %_trackbar_string%_, the default setting has been restored.',
+    'trackbar_wrong_version', '%R>>%n %_Trackbar:%_ Please upgrade your client to 0.8.9 or above if you would like to use this version of trackbar.',
+    'trackbar_not_allowed',  '%R>>%n %_Trackbar:%_ You are not allowed to use that character in the %_trackbar_string%_ setting, the default setting has been restored.',
+    'trackbar_all_removed', '%R>>%n %_Trackbar:%_ All the trackbars have been removed.',
+    'trackbar_line', '{timestamp $Z} ',
+    'trackbar_not_found', '%R>>%n %_Trackbar:%_ No trackbar found in this window.',
+    'trackbar_help', '$0',
+    'trackbar_init', '%R>>%n %_Trackbar:%_ Processed all the Irssi auto generated startup windows. Trackbar is ready.',
+]);
+
+Irssi::settings_add_str('trackbar', 'trackbar_string' => '-');
+$config{'trackbar_string'} = Irssi::settings_get_str('trackbar_string');
+
+Irssi::settings_add_str('trackbar', 'trackbar_style' => '%K');
+$config{'trackbar_style'} = Irssi::settings_get_str('trackbar_style');
+
+Irssi::signal_add(
+    'setup changed' => sub {
+        $config{'trackbar_string'} = Irssi::settings_get_str('trackbar_string');
+        $config{'trackbar_style'}  = Irssi::settings_get_str('trackbar_style');
+        if ($config{'trackbar_style'} =~ /(?<!%)[^%]|%%|%$/) {
+            Irssi::print(
+                "trackbar: %RWarning!%n 'trackbar_style' seems to contain "
+                . "printable characters. Only use format codes (read "
+                . "formats.txt).", MSGLEVEL_CLIENTERROR);
+        }
+    }
+);
+
+Irssi::signal_add(
+    'window changed' => sub {
+        my (undef, $oldwindow) = @_;
+
+        if ($oldwindow) {
+            my $line = $oldwindow->view()->get_bookmark('trackbar');
+            $oldwindow->view()->remove_line($line) if defined $line;
+            $oldwindow->print(line($oldwindow->{'width'}), MSGLEVEL_NEVER);
+            $oldwindow->view()->set_bookmark_bottom('trackbar');
+        }
+    }
+);
+
+sub line {
+    my $width  = shift;
+    my $string = $config{'trackbar_string'};
+    $string = '-' unless defined $string;
+
+    # There is a bug in irssi's utf-8 handling on config file settings, as you 
+    # can reproduce/see yourself by the following code sniplet:
+    #
+    #   my $quake = pack 'U*', 8364;    # EUR symbol
+    #   Irssi::settings_add_str 'temp', 'temp_foo' => $quake;
+    #   Irssi::print length $quake;
+    #       # prints 1
+    #   Irssi::print length Irssi::settings_get_str 'temp_foo';
+    #       # prints 3
+    #
+    #
+    # Trackbar used to have a workaround, but on recent versions of perl/irssi
+    # it does no longer work.  Therefore, if you want your trackbar to contain
+    # unicode characters, uncomment the line below for a nice full line, or set
+    # the string to whatever char you want.
+
+    # $string = pack('U*', 0x2500);
+
+
+    my $length = length $string;
+
+    if ($length == 0) {
+        $string = '-';
+        $length = 1;
+    }
+
+    my $times = $width / $length;
+    $times = int(1 + $times) if $times != int($times);
+    $string =~ s/%/%%/g;
+    return $config{'trackbar_style'} . substr($string x $times, 0, $width);
+}
+
+# Remove trackbars on upgrade - but this doesn't really work if the scripts are not loaded in the correct order... watch out!
+
+Irssi::signal_add_first( 'session save' => sub {
+            for my $window (Irssi::windows) {   
+                next unless defined $window;
+                my $line = $window->view()->get_bookmark('trackbar');
+                $window->view()->remove_line($line) if defined $line;
+            }
+        }
+);
+
+sub goto_trackbar {
+    
+    my $window = Irssi::active_win();
+    my $line = $window->view()->get_bookmark('trackbar');
+
+    if ($line) {
+        $window->command("scrollback goto ". strftime("%d %H:%M:%S", localtime($line->{'info'}->{'time'})));
+    } else {
+        $window->printformat(MSGLEVEL_CLIENTCRAP, 'trackbar_not_found');
+    }
+}
+
+sub cmd_mark {
+    my $window = Irssi::active_win();
+#    return unless defined $window;
+    my $line = $window->view()->get_bookmark('trackbar');
+    $window->view()->remove_line($line) if defined $line;
+    $window->print(line($window->{'width'}), MSGLEVEL_NEVER);
+    $window->view()->set_bookmark_bottom('trackbar');
+    Irssi::command("redraw");    
+}
+
+sub trackbar_runsub {
+    
+    my ($data, $server, $item) = @_;
+    $data =~ s/\s+$//g;
+    
+    if ($data) {
+        Irssi::command_runsub('trackbar', $data, $server, $item);
+    } else {
+        goto_trackbar();
+    }
+}
+
+Irssi::command_bind('trackbar', 'trackbar_runsub');
+Irssi::command_bind('mark',   'cmd_mark');
diff --git a/dotfiles/irssi/scripts/usercount.pl b/dotfiles/irssi/scripts/usercount.pl
new file mode 100644 (file)
index 0000000..8bbc9e0
--- /dev/null
@@ -0,0 +1,172 @@
+use Irssi 20020101.0250 ();
+$VERSION = "1.16";
+%IRSSI = (
+    authors     => 'David Leadbeater, Timo Sirainen, Georg Lukas',
+    contact     => 'dgl@dgl.cx, tss@iki.fi, georg@boerde.de',
+    name        => 'usercount',
+    description => 'Adds a usercount for a channel as a statusbar item',
+    license     => 'GNU GPLv2 or later',
+    url         => 'http://irssi.dgl.yi.org/',
+);
+
+# Once you have loaded this script run the following command:
+# /statusbar window add usercount
+# You can also add -alignment left|right option
+
+# /set usercount_show_zero on or off to show users when 0 users of that type
+# /set usercount_show_ircops (default off)
+# /set usercount_show_halfops (default on)
+
+# you can customize the look of this item from theme file:
+#  sb_usercount = "{sb %_$0%_ nicks ($1-)}";
+#  sb_uc_ircops = "%_*%_$*";
+#  sb_uc_ops = "%_@%_$*";
+#  sb_uc_halfops = "%_%%%_$*";
+#  sb_uc_voices = "%_+%_$*";
+#  sb_uc_normal = "$*";
+#  sb_uc_space = " ";
+
+
+use strict;
+use Irssi::TextUI;
+
+my ($ircops, $ops, $halfops, $voices, $normal, $total);
+my ($timeout_tag, $recalc);
+
+# Called to make the status bar item
+sub usercount {
+  my ($item, $get_size_only) = @_;
+  my $wi = !Irssi::active_win() ? undef : Irssi::active_win()->{active};
+
+  if(!ref $wi || $wi->{type} ne "CHANNEL") { # only works on channels
+    return unless ref $item;
+    $item->{min_size} = $item->{max_size} = 0;
+    return;
+  }
+
+  if ($recalc) {
+    $recalc = 0;
+    calc_users($wi);
+  }
+
+  my $theme = Irssi::current_theme();
+  my $format = $theme->format_expand("{sb_usercount}");
+  if ($format) {
+    # use theme-specific look
+    my $ircopstr = $theme->format_expand("{sb_uc_ircops $ircops}",
+          Irssi::EXPAND_FLAG_IGNORE_EMPTY);
+    my $opstr = $theme->format_expand("{sb_uc_ops $ops}",
+          Irssi::EXPAND_FLAG_IGNORE_EMPTY);
+    my $halfopstr = $theme->format_expand("{sb_uc_halfops $halfops}",
+          Irssi::EXPAND_FLAG_IGNORE_EMPTY);
+    my $voicestr = $theme->format_expand("{sb_uc_voices $voices}", 
+          Irssi::EXPAND_FLAG_IGNORE_EMPTY);
+    my $normalstr = $theme->format_expand("{sb_uc_normal $normal}",
+          Irssi::EXPAND_FLAG_IGNORE_EMPTY);
+       my $space = $theme->format_expand('{sb_uc_space}',
+         Irssi::EXPAND_FLAG_IGNORE_EMPTY);
+       $space = " " unless $space;
+
+    my $str = "";
+    $str .= $ircopstr.$space if defined $ircops;
+    $str .= $opstr.$space  if defined $ops;
+    $str .= $halfopstr.$space if defined $halfops;
+    $str .= $voicestr.$space if defined $voices;
+    $str .= $normalstr.$space if defined $normal;
+    $str =~ s/\Q$space\E$//;
+
+    $format = $theme->format_expand("{sb_usercount $total $str}",
+                                   Irssi::EXPAND_FLAG_IGNORE_REPLACES);
+  } else {
+    # use the default look
+    $format = "{sb \%_$total\%_ nicks \%c(\%n";
+    $format .= '*'.$ircops.' ' if (defined $ircops);
+    $format .= '@'.$ops.' ' if (defined $ops);
+    $format .= '%%'.$halfops.' ' if (defined $halfops);
+    $format .= "+$voices " if (defined $voices);
+    $format .= "$normal " if (defined $normal);
+    $format =~ s/ $//;
+    $format .= "\%c)}";
+  }
+
+  $item->default_handler($get_size_only, $format, undef, 1);
+}
+
+sub calc_users() {
+  my $channel = shift;
+  my $server = $channel->{server};
+
+  $ircops = $ops = $halfops = $voices = $normal = 0;
+  for ($channel->nicks()) {
+    if ($_->{serverop}) {
+      $ircops++;
+       }
+
+    if ($_->{op}) {
+      $ops++;
+       } elsif ($_->{halfop}) {
+          $halfops++;
+    } elsif ($_->{voice}) {
+      $voices++;
+    } else {
+      $normal++;
+    }
+  }
+
+  $total = $ops+$halfops+$voices+$normal;
+  if (!Irssi::settings_get_bool('usercount_show_zero')) {
+    $ircops = undef if ($ircops == 0);
+    $ops = undef if ($ops == 0);
+    $halfops = undef if ($halfops == 0);
+    $voices = undef if ($voices == 0);
+    $normal = undef if ($normal == 0);
+  }
+  $halfops = undef unless Irssi::settings_get_bool('usercount_show_halfops');
+  $ircops = undef unless Irssi::settings_get_bool('usercount_show_ircops');
+}
+
+sub refresh {
+   if ($timeout_tag > 0) {
+      Irssi::timeout_remove($timeout_tag);
+      $timeout_tag = 0;
+   }
+   Irssi::statusbar_items_redraw('usercount');
+}
+
+sub refresh_check {
+   my $channel = shift;
+   my $wi = ref Irssi::active_win() ? Irssi::active_win()->{active} : 0;
+
+   return unless ref $wi && ref $channel;
+   return if $wi->{name} ne $channel->{name};
+   return if $wi->{server}->{tag} ne $channel->{server}->{tag};
+
+   # don't refresh immediately, or we'll end up refreshing 
+   # a lot around netsplits
+   $recalc = 1;
+   Irssi::timeout_remove($timeout_tag) if ($timeout_tag > 0);
+   $timeout_tag = Irssi::timeout_add(500, 'refresh', undef);
+}
+
+sub refresh_recalc {
+  $recalc = 1;
+  refresh();
+}
+
+$recalc = 1;
+$timeout_tag = 0;
+
+Irssi::settings_add_bool('usercount', 'usercount_show_zero', 1);
+Irssi::settings_add_bool('usercount', 'usercount_show_ircops', 0);
+Irssi::settings_add_bool('usercount', 'usercount_show_halfops', 1);
+
+Irssi::statusbar_item_register('usercount', undef, 'usercount');
+Irssi::statusbars_recreate_items();
+
+Irssi::signal_add_last('nicklist new', 'refresh_check');
+Irssi::signal_add_last('nicklist remove', 'refresh_check');
+Irssi::signal_add_last('nick mode changed', 'refresh_check');
+Irssi::signal_add_last('setup changed', 'refresh_recalc');
+Irssi::signal_add_last('window changed', 'refresh_recalc');
+Irssi::signal_add_last('window item changed', 'refresh_recalc');
+
diff --git a/dotfiles/irssi/scripts/xchatnickcolor.pl b/dotfiles/irssi/scripts/xchatnickcolor.pl
new file mode 100644 (file)
index 0000000..8697d79
--- /dev/null
@@ -0,0 +1,157 @@
+use strict;
+use Irssi 20020101.0250 ();
+use vars qw($VERSION %IRSSI); 
+$VERSION = "1";
+%IRSSI = (
+    authors     => "Timo Sirainen, Ian Peters",
+    contact    => "tss\@iki.fi", 
+    name        => "Nick Color",
+    description => "assign a different color for each nick",
+    license    => "Public Domain",
+    url                => "http://irssi.org/",
+    changed    => "2002-03-04T22:47+0100"
+);
+
+# hm.. i should make it possible to use the existing one..
+Irssi::theme_register([
+  'pubmsg_hilight', '{pubmsghinick $0 $3 $1}$2'
+]);
+
+my %saved_colors;
+my %session_colors = {};
+my @colors = qw/ 2 3 4 5 6 7 9 10 11 12 13 14 15/;
+
+sub load_colors {
+  open COLORS, "$ENV{HOME}/.irssi/saved_colors";
+
+  while (<COLORS>) {
+    # I don't know why this is necessary only inside of irssi
+    my @lines = split "\n";
+    foreach my $line (@lines) {
+      my($nick, $color) = split ":", $line;
+      $saved_colors{$nick} = $color;
+    }
+  }
+
+  close COLORS;
+}
+
+sub save_colors {
+  open COLORS, ">$ENV{HOME}/.irssi/saved_colors";
+
+  foreach my $nick (keys %saved_colors) {
+    print COLORS "$nick:$saved_colors{$nick}\n";
+  }
+
+  close COLORS;
+}
+
+# If someone we've colored (either through the saved colors, or the hash
+# function) changes their nick, we'd like to keep the same color associated
+# with them (but only in the session_colors, ie a temporary mapping).
+
+sub sig_nick {
+  my ($server, $newnick, $nick, $address) = @_;
+  my $color;
+
+  $newnick = substr ($newnick, 1) if ($newnick =~ /^:/);
+
+  if ($color = $saved_colors{$nick}) {
+    $session_colors{$newnick} = $color;
+  } elsif ($color = $session_colors{$nick}) {
+    $session_colors{$newnick} = $color;
+  }
+}
+
+# This gave reasonable distribution values when run across
+# /usr/share/dict/words
+
+sub simple_hash {
+  my ($string) = @_;
+  chomp $string;
+  my @chars = split //, $string;
+  my $counter;
+
+  foreach my $char (@chars) {
+    $counter += ord $char;
+  }
+
+  $counter = $colors[$counter % 11];
+
+  return $counter;
+}
+
+# FIXME: breaks /HILIGHT etc.
+sub sig_public {
+  my ($server, $msg, $nick, $address, $target) = @_;
+  my $chanrec = $server->channel_find($target);
+  return if not $chanrec;
+  my $nickrec = $chanrec->nick_find($nick);
+  return if not $nickrec;
+  my $nickmode = $nickrec->{op} ? "@" : $nickrec->{voice} ? "+" : "";
+
+  # Has the user assigned this nick a color?
+  my $color = $saved_colors{$nick};
+
+  # Have -we- already assigned this nick a color?
+  if (!$color) {
+    $color = $session_colors{$nick};
+  }
+
+  # Let's assign this nick a color
+  if (!$color) {
+    $color = simple_hash $nick;
+    $session_colors{$nick} = $color;
+  }
+
+  $color = "0".$color if ($color < 10);
+  $server->command('/^format pubmsg %b<%w$2'.chr(3).$color.'$[-11]0%b> %K|%n $1');
+ # $server->command('/^format action_public {pubaction '.chr(3).$color.'$0}$1');
+}
+
+sub cmd_color {
+  my ($data, $server, $witem) = @_;
+  my ($op, $nick, $color) = split " ", $data;
+
+  $op = lc $op;
+
+  if (!$op) {
+    Irssi::print ("No operation given");
+  } elsif ($op eq "save") {
+    save_colors;
+  } elsif ($op eq "set") {
+    if (!$nick) {
+      Irssi::print ("Nick not given");
+    } elsif (!$color) {
+      Irssi::print ("Color not given");
+    } elsif ($color < 2 || $color > 14) {
+      Irssi::print ("Color must be between 2 and 14 inclusive");
+    } else {
+      $saved_colors{$nick} = $color;
+    }
+  } elsif ($op eq "clear") {
+    if (!$nick) {
+      Irssi::print ("Nick not given");
+    } else {
+      delete ($saved_colors{$nick});
+    }
+  } elsif ($op eq "list") {
+    Irssi::print ("\nSaved Colors:");
+    foreach my $nick (keys %saved_colors) {
+      Irssi::print (chr (3) . "$saved_colors{$nick}$nick" .
+                   chr (3) . "1 ($saved_colors{$nick})");
+    }
+  } elsif ($op eq "preview") {
+    Irssi::print ("\nAvailable colors:");
+    foreach my $i (2..14) {
+      Irssi::print (chr (3) . "$i" . "Color #$i");
+    }
+  }
+}
+
+load_colors;
+
+Irssi::command_bind('color', 'cmd_color');
+
+Irssi::signal_add('message public', 'sig_public');
+Irssi::signal_add('event nick', 'sig_nick');