🢀︎ weechat :: c5e24a3


commit c5e24a351e9f18bb0963f3c537317dcbdf471a06
Author: acidvegas <acid.vegas@acid.vegas>
Date:   Sat Jul 20 03:21:21 2019 -0400

    updated

diff --git a/README.md b/README.md
index 0930860..49b57c7 100644
--- a/README.md
+++ b/README.md
@@ -1,5 +1,319 @@
-###### Settings
+# WeeChat
+> backup of my weechat setup
+
+## Table of Contents
+- [Requirements](#requirements)
+- [Setup](#setup)
+- [Key Bindings](#key-bindings)
+- [Settings](#settings)
+- [Triggers](#triggers)
+- [Servers](#servers)
+- [Services](#services)
+- [Proxy](#proxy)
+- [Relay](#relay)
+- [Notes](#notes)
+
+### Requirements
+- [python-potr](https://pypi.org/project/python-potr/)
+
+### Setup
 ```
+#!/bin/sh
+cd $HOME/.weechat && rm -r fifo.conf logger.conf lua.conf weechat.log xfer.conf logs/ lua/ xfer/
+git clone --depth 1 https://github.com/acidvegas/weechat.git $HOME/weechat
+mv $HOME/weechat/alias.conf $HOME/.weechat/alias.conf
+mv $HOME/weechat/scripts/*.pl $HOME/.weechat/perl/autoload/ && mv $HOME/weechat/scripts/*.py $HOME/.weechat/python/autoload/
+nano $HOME/.weechat/cert.pem && chmod 400 $HOME/.weechat/cert.pem
+```
+
+### Key Bindings
+| Key             | Description                        |
+| --------------- | ---------------------------------- |
+| Alt  + l        | Toggle bare display mode           |
+| Alt  + Home/End | Go to buffer top or bottom         |
+| Alt  + n/p      | Scroll highlights                  |
+| Alt  + u        | Go to first unread message         |
+| Ctrl + r        | Search for text *(Ctrl+m to stop)* |
+| Ctrl + y        | Paste clipboard contents           |
+| Ctrl + Up/Down  | Cycle global history               |
+| Page Up/Down    | Go up or down a page               |
+
+### Settings
+```
+/bar del status
+/key unbind ctrl-A
+/key unbind ctrl-B
+/key unbind ctrl-D
+/key unbind ctrl-E
+/key unbind ctrl-F
+/key unbind ctrl-H
+/key unbind ctrl-I
+/key unbind ctrl-J
+/key unbind ctrl-K
+/key unbind ctrl-L
+/key unbind ctrl-N
+/key unbind ctrl-P
+/key unbind ctrl-R
+/key unbind ctrl-Sctrl-U
+/key unbind ctrl-T
+/key unbind ctrl-U
+/key unbind ctrl-W
+/key unbind ctrl-X
+/key unbind meta-meta-OP
+/key unbind meta-meta-OQ
+/key unbind meta-meta2-11~
+/key unbind meta-meta2-12~
+/key unbind meta-meta2-1~
+/key unbind meta-meta2-23~
+/key unbind meta-meta2-24~
+/key unbind meta-meta2-4~
+/key unbind meta-meta2-5~
+/key unbind meta-meta2-6~
+/key unbind meta-meta2-7~
+/key unbind meta-meta2-8~
+/key unbind meta-meta2-A
+/key unbind meta-meta2-B
+/key unbind meta-meta2-C
+/key unbind meta-meta2-D
+/key unbind meta--
+/key unbind meta-/
+/key unbind meta-0
+/key unbind meta-2
+/key unbind meta-3
+/key unbind meta-4
+/key unbind meta-5
+/key unbind meta-6
+/key unbind meta-7
+/key unbind meta-8
+/key unbind meta-9
+/key unbind meta-<
+/key unbind meta-=
+/key unbind meta->
+/key unbind meta-OA
+/key unbind meta-OB
+/key unbind meta-OC
+/key unbind meta-OD
+/key unbind meta-OF
+/key unbind meta-OH
+/key unbind meta-Oa
+/key unbind meta-Ob
+/key unbind meta-Oc
+/key unbind meta-Od
+/key unbind meta2-15~
+/key unbind meta2-17~
+/key unbind meta2-18~
+/key unbind meta2-19~
+/key unbind meta2-1;3C
+/key unbind meta2-1;3D
+/key unbind meta2-1;3F
+/key unbind meta2-1;3H
+/key unbind meta2-1;5C
+/key unbind meta2-1;5D
+/key unbind meta2-1~
+/key unbind meta2-200~
+/key unbind meta2-201~
+/key unbind meta2-20~
+/key unbind meta2-21~
+/key unbind meta2-23;3~
+/key unbind meta2-23;5~
+/key unbind meta2-23^
+/key unbind meta2-23~
+/key unbind meta2-24;3~
+/key unbind meta2-24;5~
+/key unbind meta2-24^
+/key unbind meta2-24~
+/key unbind meta2-3~
+/key unbind meta2-4~
+/key unbind meta2-5;3~
+/key unbind meta2-6;3~
+/key unbind meta2-7~
+/key unbind meta2-8~
+/key unbind meta2-F
+/key unbind meta2-G
+/key unbind meta2-H
+/key unbind meta2-I
+/key unbind meta2-[E
+/key unbind meta-_
+/key unbind meta-a
+/key unbind meta-b
+/key unbind meta-d
+/key unbind meta-f
+/key unbind meta-h
+/key unbind meta-jmeta-f
+/key unbind meta-jmeta-l
+/key unbind meta-jmeta-r
+/key unbind meta-jmeta-s
+/key unbind meta-j01
+/key unbind meta-j02
+/key unbind meta-j03
+/key unbind meta-j04
+/key unbind meta-j05
+/key unbind meta-j06
+/key unbind meta-j07
+/key unbind meta-j08
+/key unbind meta-j09
+/key unbind meta-j10
+/key unbind meta-j11
+/key unbind meta-j12
+/key unbind meta-j13
+/key unbind meta-j14
+/key unbind meta-j15
+/key unbind meta-j16
+/key unbind meta-j17
+/key unbind meta-j18
+/key unbind meta-j19
+/key unbind meta-j20
+/key unbind meta-j21
+/key unbind meta-j22
+/key unbind meta-j23
+/key unbind meta-j24
+/key unbind meta-j25
+/key unbind meta-j26
+/key unbind meta-j27
+/key unbind meta-j28
+/key unbind meta-j29
+/key unbind meta-j30
+/key unbind meta-j31
+/key unbind meta-j32
+/key unbind meta-j33
+/key unbind meta-j34
+/key unbind meta-j35
+/key unbind meta-j36
+/key unbind meta-j37
+/key unbind meta-j38
+/key unbind meta-j39
+/key unbind meta-j40
+/key unbind meta-j41
+/key unbind meta-j42
+/key unbind meta-j43
+/key unbind meta-j44
+/key unbind meta-j45
+/key unbind meta-j46
+/key unbind meta-j47
+/key unbind meta-j48
+/key unbind meta-j49
+/key unbind meta-j50
+/key unbind meta-j51
+/key unbind meta-j52
+/key unbind meta-j53
+/key unbind meta-j54
+/key unbind meta-j55
+/key unbind meta-j56
+/key unbind meta-j57
+/key unbind meta-j58
+/key unbind meta-j59
+/key unbind meta-j60
+/key unbind meta-j61
+/key unbind meta-j62
+/key unbind meta-j63
+/key unbind meta-j64
+/key unbind meta-j65
+/key unbind meta-j66
+/key unbind meta-j67
+/key unbind meta-j68
+/key unbind meta-j69
+/key unbind meta-j70
+/key unbind meta-j71
+/key unbind meta-j72
+/key unbind meta-j73
+/key unbind meta-j74
+/key unbind meta-j75
+/key unbind meta-j76
+/key unbind meta-j77
+/key unbind meta-j78
+/key unbind meta-j79
+/key unbind meta-j80
+/key unbind meta-j81
+/key unbind meta-j82
+/key unbind meta-j83
+/key unbind meta-j84
+/key unbind meta-j85
+/key unbind meta-j86
+/key unbind meta-j87
+/key unbind meta-j88
+/key unbind meta-j89
+/key unbind meta-j90
+/key unbind meta-j91
+/key unbind meta-j92
+/key unbind meta-j93
+/key unbind meta-j94
+/key unbind meta-j95
+/key unbind meta-j96
+/key unbind meta-j97
+/key unbind meta-j98
+/key unbind meta-j99
+/key unbind meta-k
+/key unbind meta-m
+/key unbind meta-r
+/key unbind meta-s
+/key unbind meta-wmeta-meta2-A
+/key unbind meta-wmeta-meta2-B
+/key unbind meta-wmeta-meta2-C
+/key unbind meta-wmeta-meta2-D
+/key unbind meta-wmeta2-1;3A
+/key unbind meta-wmeta2-1;3B
+/key unbind meta-wmeta2-1;3C
+/key unbind meta-wmeta2-1;3D
+/key unbind meta-wmeta-b
+/key unbind meta-wmeta-s
+/key unbind meta-x
+/key unbind meta-z
+/key unbind ctrl-_
+/key unbindctxt search ctrl-I
+/key unbindctxt search ctrl-J
+/key unbindctxt search ctrl-Q
+/key unbindctxt search ctrl-R
+/key unbindctxt search meta-c
+/key unbindctxt cursor ctrl-J
+/key unbindctxt cursor ctrl-M
+/key unbindctxt cursor meta-meta2-A
+/key unbindctxt cursor meta-meta2-B
+/key unbindctxt cursor meta-meta2-C
+/key unbindctxt cursor meta-meta2-D
+/key unbindctxt cursor meta2-1;3A
+/key unbindctxt cursor meta2-1;3B
+/key unbindctxt cursor meta2-1;3C
+/key unbindctxt cursor meta2-1;3D
+/key unbindctxt cursor meta2-A
+/key unbindctxt cursor meta2-B
+/key unbindctxt cursor meta2-C
+/key unbindctxt cursor meta2-D
+/key unbindctxt cursor @item(buffer_nicklist):K
+/key unbindctxt cursor @item(buffer_nicklist):b
+/key unbindctxt cursor @item(buffer_nicklist):k
+/key unbindctxt cursor @item(buffer_nicklist):q
+/key unbindctxt cursor @item(buffer_nicklist):w
+/key unbindctxt cursor @chat:Q
+/key unbindctxt cursor @chat:m
+/key unbindctxt cursor @chat:q
+/key unbindctxt mouse @bar(input):button2
+/key unbindctxt mouse @bar(nicklist):button1-gesture-down
+/key unbindctxt mouse @bar(nicklist):button1-gesture-down-long
+/key unbindctxt mouse @bar(nicklist):button1-gesture-up
+/key unbindctxt mouse @bar(nicklist):button1-gesture-up-long
+/key unbindctxt mouse @chat(fset.fset):button1
+/key unbindctxt mouse @chat(fset.fset):button2*
+/key unbindctxt mouse @chat(script.scripts):button1
+/key unbindctxt mouse @chat(script.scripts):button2
+/key unbindctxt mouse @item(buffer_nicklist):button1
+/key unbindctxt mouse @item(buffer_nicklist):button1-gesture-left
+/key unbindctxt mouse @item(buffer_nicklist):button1-gesture-left-long
+/key unbindctxt mouse @item(buffer_nicklist):button2
+/key unbindctxt mouse @item(buffer_nicklist):button2-gesture-left
+/key unbindctxt mouse @item(buflist):button1*
+/key unbindctxt mouse @item(buflist):button2*
+/key unbindctxt mouse @item(buflist2):button1*
+/key unbindctxt mouse @item(buflist2):button2*
+/key unbindctxt mouse @item(buflist3):button1*
+/key unbindctxt mouse @item(buflist3):button2*
+/key unbindctxt mouse @chat:button1
+/key unbindctxt mouse @chat:button1-gesture-left
+/key unbindctxt mouse @chat:button1-gesture-left-long
+/key unbindctxt mouse @chat:button1-gesture-right
+/key unbindctxt mouse @chat:button1-gesture-right-long
+/key unbindctxt mouse @chat:ctrl-wheeldown
+/key unbindctxt mouse @chat:ctrl-wheelup
+/key unbindctxt mouse @*:button3
 /set buflist.look.mouse_wheel				off
 /set weechat.plugin.autoload				"*,!fifo,!guile,!logger,!lua,!ruby,!spell,!tcl,!xfer"
 /set weechat.startup.display_logo			off
@@ -11,7 +325,9 @@
 /set weechat.look.buffer_time_format		" %H:%M"
 /set weechat.look.confirm_quit				on
 /set weechat.look.day_change				off
+/set weechat.look.highlight					acidvegas,supernets
 /set weechat.look.item_buffer_filter		"•"
+/set weechat.look.mouse						on
 /set weechat.look.prefix_align_max			15
 /set weechat.look.prefix_join				"▬▬▶"
 /set weechat.look.prefix_quit				"◀▬▬"
@@ -41,11 +357,7 @@
 /set weechat.bar.input.items				"buffer_name+(buffer_modes)+[buffer_nicklist_count],[input_prompt]+(away),[input_search],[input_paste],input_text"
 /set weechat.bar.input.separator			off
 /set weechat.bar.nicklist.size_max			15
-/set weechat.bar.status.color_bg			default
-/set weechat.bar.status.color_delim			darkgray
-/set weechat.bar.status.conditions			"${window.buffer.full_name} != perl.highmon"
-/set weechat.bar.status.items				"buffer_number+:+buffer_name+(buffer_modes)+[buffer_nicklist_count]"
-/set weechat.bar.status.separator			off
+/set weechat.bar.status.hidden				on
 /set weechat.bar.title.color_bg				black
 /set weechat.bar.title.separator			off
 /set weechat.bar.title.size_max				2
@@ -94,9 +406,25 @@
 /set plugins.var.perl.fuckyou.forcepart		sapart
 /set plugins.var.perl.highmon.first_run		false
 /set plugins.var.perl.highmon.short_names	on
+/set sec.crypt.hash_algo					sha512
+/set sec.crypt.passphrase_file				$HOME/.weechat-pass
+```
+
+### Triggers
+```
+/trigger add greentext modifier "weechat_print" "${tg_message_nocolor} =~ ^>[^:._]" "/(.*)/${tg_prefix}\t${color:34}${tg_message}" ""
+/trigger add url_color modifier "weechat_print" "${tg_tags} !~ irc_quit" ";[a-z]+://\S+;${color:32}${re:0}${color:reset};" ""
+
+/trigger add osd print '' '${tg_highlight}' '/.*/${weechat.look.nick_prefix}${tg_prefix_nocolor}${weechat.look.nick_suffix} ${tg_message_nocolor}/ /&/&amp;/ /[\\]/&#92;/ /"/&quot;/ /</&lt;/ />/&gt;/' '/exec -norc -nosw notify-send -i weechat "${buffer.full_name}" "${tg_message}"'
+/trigger add osd2 print '' '(${tg_highlight} || ${tg_tag_notify} == private) && ${buffer.notify} > 0' '/.*/${weechat.look.nick_prefix}${tg_prefix_nocolor}${weechat.look.nick_suffix} ${tg_message_nocolor}/ /&/&amp;/ /[\\]/&#92;/ /"/&quot;/ /</&lt;/ />/&gt;/' '/exec -norc -nosw notify-send -i weechat "${buffer.full_name}" "${tg_message}"'
+
+/trigger add replacebox modifier irc_out1_PRIVMSG "" "/\[x\]/☑/ /\[ \]/☐"
+
+/trigger add relay_awayclear signal relay_client_connected "" "" "/away -all"
+/trigger add relay_setaway signal relay_client_disconnected "" "" "/away -all I am away"
 ```
 
-###### Servers
+### Servers
 ```
 /server add 711chan		irc.711chan.org/6697 -ssl
 /server add blackhat	breaking.technology/6697 -ssl
@@ -123,7 +451,7 @@
 /set irc.server.unreal.usermode					"-x"
 ```
 
-###### Services
+### Services
 ```
 /secure passphrase CHANGEME
 /secure set networkname CHANGEME
@@ -145,4 +473,27 @@
 /msg NickServ SET SECURE ON
 /msg HostServ REQUEST <mask>
 /msg HostServ ON
-```
\ No newline at end of file
+```
+
+### Proxy
+```
+/proxy add tor socks5 127.0.0.1 9050
+/set irc.server.networkname.proxy "tor"
+```
+
+### Relay
+```
+/set relay.network.password "mypassword"
+/relay add ssl.weechat 9000
+/set relay.network.totp_secret "secretpasswordbase32"
+~/.weechat/ssl/relay.pem 
+/set relay.network.password
+/set relay.network.max_clients 1
+
+cat /etc/letsencrypt/live/localhost/{fullchain,privkey}.pem > ~username/.weechat/ssl/relay.pem
+chown -R username:username ~username/.weechat/ssl/
+/relay sslcertkey
+```
+
+### Notes
+All scripts are updated as of *7/20/2019*
\ No newline at end of file
diff --git a/scripts/colourflood.pl b/scripts/cflood.pl
similarity index 54%
rename from scripts/colourflood.pl
rename to scripts/cflood.pl
index 49825da..f236a72 100644
--- a/scripts/colourflood.pl
+++ b/scripts/cflood.pl
@@ -1,40 +1,12 @@
-#{{{ BSD License
-# Copyright (c) 2008 hzu/zionist
-# All rights reserved.
-#
-# Redistribution and use in source and binary forms, with or
-# without modification, are permitted provided that the following
-# conditions are met:
-#
-# 1. Redistributions of source code must retain the above copyright
-#    notice, this list of conditions and the following disclaimer.
-# 2. The name of the author may not be used to endorse or promote products
-#    derived from this software without specific prior written permission.
-#
-# THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES,
-# INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY
-# AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL
-# THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
-# EXEMPLARY, OR CONSEQUENTIAL  DAMAGES (INCLUDING, BUT NOT LIMITED TO,
-# PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
-# OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
-# WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
-# OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
-# ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
-#}}}
+#!/usr/bin/perl
+# original source by hzu & ported by wowaname (https://git.volatile.bz/cgit/wowaname/weechat-etc/plain/scripts/colourflood.pl)
+# modified by acidvegas (https://acid.vegas/weechat)
 
-# Ported from irssi to WeeChat by the Krusty Krab
 
 use strict;
 use warnings;
 no strict 'subs';
 
-my $SCRIPT_NAME = 'colourflood';
-my $SCRIPT_AUTHOR = 'hzu';
-my $SCRIPT_VERSION = '0.3';
-my $SCRIPT_LICENCE = 'BSD';
-my $SCRIPT_DESC = 'A-rab style ircing';
-
 my $USAGE = <<EOF;
 options:
     -r      Random back & foreground colours (default)
@@ -70,25 +42,6 @@ our %clr = (
   lightgray    => 15,
 );
 
-if (weechat::register($SCRIPT_NAME, $SCRIPT_AUTHOR, $SCRIPT_VERSION,
- $SCRIPT_LICENCE, $SCRIPT_DESC, '', '')) {
-	weechat::hook_command('cflood', $SCRIPT_DESC, '[options] text',
-		$USAGE, '', 'cmd_cflood', '');
-
-	my %OPTIONS = (
-		dir => ['Database directory',
-			weechat::info_get('weechat_dir', '').'/yiffs'],
-		db => ['Default database', 'yiffs'],
-		solodb => ['Default database when nick is omitted', 'solos'],
-		);
-
-	for my $option (keys %OPTIONS) {
-		weechat::config_set_plugin($option, $OPTIONS{$option}[1])
-		 unless weechat::config_is_set_plugin($option);
-		weechat::config_set_desc_plugin($option, $OPTIONS{$option}[0]);
-	}
-}
-
 sub colour {
 	my ($fg, $bg, $text) = @_;
 	my $fore = ($fg =~ /^[0-9][0-9]?$/) ? $fg : $clr{$fg};
@@ -102,7 +55,6 @@ sub colour {
 sub parse {
 	my @args = ( split / +/, shift );
 	my ( %todo, $text, $body );
-
 	while ( ($_ = shift @args) ne '' ) {
 		/^-r$/ and next;
 		/^-f$/ and $todo{f} = shift @args, next;
@@ -113,7 +65,6 @@ sub parse {
 		$text = @args < 1 ? $_ : "$_ " . join ' ', @args;
 		last;
 	}
-
 	if (!(defined $todo{fg}) || !(defined $todo{bg})) {
 		$body = "";
 		my @rnd_clr = keys %clr;
@@ -135,16 +86,15 @@ sub parse {
 sub cmd_cflood {
 	my (undef, $buffer, $data) = @_;
 	my $ret;
-
 	return weechat::WEECHAT_RC_OK if ($data eq '');
-
 	chomp( $ret = parse($data) );
-
 	if ($ret =~ /\n/) {
 		map { weechat::command($buffer, "/msg * $_") } (split /\n/, $ret);
 	} else { 
 		weechat::command($buffer, "/msg * $ret");
 	}
-
 	return weechat::WEECHAT_RC_OK;
 }
+
+weechat::register("cflood", "acidvegas", "1.0", "ISC", "random text foreground/background", "", ""))
+weechat::hook_command("cflood", "random text foreground/background", "[options] text", $USAGE, "", "cmd_cflood", "");
\ No newline at end of file
diff --git a/scripts/clone_scanner.py b/scripts/clone_scanner.py
new file mode 100644
index 0000000..e8205c2
--- /dev/null
+++ b/scripts/clone_scanner.py
@@ -0,0 +1,514 @@
+# -*- coding: utf-8 -*-
+#
+# Clone Scanner, version 1.3 for WeeChat version 0.3
+# Latest development version: https://github.com/FiXato/weechat_scripts
+#
+#   A Clone Scanner that can manually scan channels and
+#   automatically scans joins for users on the channel
+#   with multiple nicknames from the same host.
+#
+#   Upon join by a user, the user's host is compared to the infolist of
+#   already connected users to see if they are already online from
+#   another nickname. If the user is a clone, it will report it.
+#   With the '/clone_scanner scan' command you can manually scan a chan.
+#
+#   See /set plugins.var.python.clone_scanner.* for all possible options
+#   Use the brilliant iset.pl plugin (/weeget install iset) to see what they do
+#   Or check the sourcecode below.
+#
+# Example output for an on-join scan result:
+#   21:32:46  ▬▬▶ FiXato_Odie (FiXato@FiXato.net) has joined #lounge
+#   21:32:46      FiXato_Odie is already on the channel as FiXato!FiXato@FiXato.Net and FiX!FiXaphone@FiXato.net
+#
+# Example output for a manual scan:
+#   21:34:44 fixato.net is online from 3 nicks:
+#   21:34:44  - FiXato!FiXato@FiXato.Net
+#   21:34:44  - FiX!FiXaphone@FiXato.net
+#   21:34:44  - FiXato_Odie!FiXato@FiXato.net
+#
+## History:
+### 2011-09-11: FiXato:
+#
+# * version 0.1: initial release.
+#     * Added an on-join clone scan. Any user that joins a channel will be
+#       matched against users already on the channel.
+#
+# * version 0.2: manual clone scan
+#     * Added a manual clone scan via /clone_scanner scan
+#        you can specify a target channel with:
+#         /clone_scanner scan #myChannelOnCurrentServer
+#        or:
+#         /clone_scanner scan Freenode.#myChanOnSpecifiedNetwork
+#     * Added completion
+#
+### 2011-09-12: FiXato:
+#
+# * version 0.3: Refactor galore
+#     * Refactored some code. Codebase should be DRYer and clearer now.
+#     * Manual scan report lists by host instead of nick now.
+#     * Case-insensitive host-matching
+#     * Bugfixed the infolist memleak.
+#     * on-join scanner works again
+#     * Output examples added to the comments
+#
+### 2011-09-19
+# * version 0.4: Option galore
+#     * Case-insensitive buffer lookup fix.
+#     * Made most messages optional through settings.
+#     * Made on-join alert and clone report key a bit more configurable.
+#     * Added formatting options for on-join alerts.
+#     * Added format_message helper method that accepts multiple whitespace-separated weechat.color() options.
+#     * Added formatting options for join messages
+#     * Added formatting options for clone reports
+#     * Added format_from_config helper method that reads the given formatting key from the config
+#
+# * version 0.5: cs_buffer refactoring
+#     * dropping the manual cs_create_buffer call in favor for a cs_get_buffer() method
+#
+### 2012-02-10: FiXato:
+#
+# * version 0.6: Stop shoving that buffer in my face!
+#     * The clone_scanner buffer should no longer pop up by itself when you load the script.
+#       It should only pop up now when you actually a line needs to show up in the buffer.
+#
+# * version 0.7: .. but please pop it up in my current window when I ask for it
+#     * Added setting plugins.var.python.clone_scanner.autofocus
+#       This will autofocus the clone_scanner buffer in the current window if another window isn't
+#       already showing it, and of course only when the clone_scanner buffer is triggered
+#
+### 2012-02-10: FiXato:
+#
+# * version 0.8: .. and only when it is first created..
+#     * Prevents the buffer from being focused every time there is activity in it and not being shown in a window.
+#
+### 2012-04-01: FiXato:
+#
+# * version 0.9: Hurrah for bouncers...
+#     * Added the option plugins.var.python.clone_scanner.compare_idents
+#       Set it to 'on' if you don't want people with different idents to be marked as clones.
+#       Useful on channels with bouncers.
+#
+### 2012-04-02: FiXato:
+#
+# * version 1.0: Bugfix
+#     * Fixed the on-join scanner bug introduced by the 0.9 release.
+#       I was not properly comparing the new ident@host.name key in all places yet.
+#       Should really have tested this better ><
+#
+### 2012-04-03: FiXato:
+#
+# * version 1.1: Stop being so sensitive!
+#     * Continuing to fix the on-join scanner bugs introduced by the 0.9 release.
+#       The ident@host.name dict key wasn't being lowercased for comparison in the on-join scan.
+#
+# * version 1.2: So shameless!
+#     * Added shameless advertising for my script through /clone_scanner advertise
+#
+### 2013-04-09: FiXato:
+# * version 1.3: Such a killer rabbit
+#     * Thanks to Curtis Sorensen aka killerrabbit clone_scanner.py now supports:
+#       * local channels (&-prefixed)
+#       * nameless channels (just # or &)
+#
+### 2014-12-07: FiXato:
+# * version 1.4: Inefficiency Warning Patch
+# WARNING! I recently noticed the  clone_scanner script is currently rather inefficient, and requires a rewrite.
+#   It may cause nasty lag when there are a lot of concurrent joins on the channel, as it evaluates the nicklist on every join.
+#   For servers like twitch.tv and bitlbee, you might want to exclude the server with the new setting:
+#    /set plugins.var.python.clone_scanner.hooks.excluded_servers twitchtv,bitlbee
+#   This script update is an emergency update to add the above option, and warn the users of this script.
+#   You can disable this warning with: /set plugins.var.python.clone_scanner.lag_warning off
+#
+# * Other patches in this version include:
+#     * Re-did how settings are handled, using nils_2's skeleton code
+#     * Settings are now automatically memoized when changed
+#     * Added options hooks.excluded_servers, hooks.explicit_servers and lag_warning
+#     * Updated advertise option to include /script install clone_scanner.py
+#     * Updated min version to 0.3.6, though this will prob change in the next version
+#
+## Acknowledgements:
+# * Sebastien "Flashcode" Helleu, for developing the kick-ass chat/IRC
+#    client WeeChat
+# * ArZa, whose kickban.pl script helped me get started with using the
+#   infolist results.
+# * LayBot, for requesting the ident comparison
+# * Curtis "killerrabbit" Sorensen, for sending in two pull-requests,
+#   adding support for local and nameless channels.
+# * nils_2 aka weechatter, for providing the excellent skeleton.py script
+#
+## TODO:
+#   - REWRITE TO IMPROVE EFFICIENCY:
+#     - Probably only do the infolist loop on self-join,
+#       and from then on manually keep track of join/parts/quit/nick-changes
+#   - Add option to enable/disable public clone reporting aka msg channels
+#   - Add option to enable/disable scanning on certain channels
+#   - Add cross-channel clone scan
+#   - Add cross-server clone scan
+#
+## Copyright (c) 2011-2014 Filip H.F. "FiXato" Slagter,
+#   <FiXato [at] Gmail [dot] com>
+#   http://profile.fixato.org
+#
+# Permission is hereby granted, free of charge, to any person obtaining
+# a copy of this software and associated documentation files (the
+# "Software"), to deal in the Software without restriction, including
+# without limitation the rights to use, copy, modify, merge, publish,
+# distribute, sublicense, and/or sell copies of the Software, and to
+# permit persons to whom the Software is furnished to do so, subject to
+# the following conditions:
+#
+# The above copyright notice and this permission notice shall be
+# included in all copies or substantial portions of the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+# NON-INFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+#
+SCRIPT_NAME     = "clone_scanner"
+SCRIPT_AUTHOR   = "Filip H.F. 'FiXato' Slagter <fixato [at] gmail [dot] com>"
+SCRIPT_VERSION  = "1.4"
+SCRIPT_LICENSE  = "MIT"
+SCRIPT_DESC     = "A Clone Scanner that can manually scan channels and automatically scans joins for users on the channel with multiple nicknames from the same host."
+SCRIPT_COMMAND  = "clone_scanner"
+SCRIPT_CLOSE_CB = "cs_close_cb"
+
+import_ok = True
+
+try:
+  import weechat
+except ImportError:
+  print "This script must be run under WeeChat."
+  import_ok = False
+
+import re
+cs_buffer = None
+OPTIONS = {
+  "autofocus":                           ("on", "Focus the clone_scanner buffer in the current window if it isn't already displayed by a window."),
+  "compare_idents":                      ("off", "Match against ident@host.name instead of just the hostname. Useful if you don't want different people from bouncers marked as clones"),
+  "display_join_messages":               ("off", "Display all joins in the clone_scanner buffer"),
+  "display_onjoin_alert_clone_buffer":   ("on", "Display an on-join clone alert in the clone_scanner buffer"),
+  "display_onjoin_alert_target_buffer":  ("on", "Display an on-join clone alert in the buffer where the clone was detected"),
+  "display_onjoin_alert_current_buffer": ("off", "Display an on-join clone alert in the current buffer"),
+  "display_scan_report_clone_buffer":    ("on", "Display manual scan reports in the clone buffer"),
+  "display_scan_report_target_buffer":   ("off", "Display manual scan reports in the buffer of the scanned channel"),
+  "display_scan_report_current_buffer":  ("on", "Display manual scan reports in the current buffer"),
+
+  "clone_report_key":                    ("mask", "Which 'key' to display in the clone report: 'mask' for full hostmasks, or 'nick' for nicks"),
+  "clone_onjoin_alert_key":              ("mask", "Which 'key' to display in the on-join alerts: 'mask' for full hostmasks, or 'nick' for nicks"),
+
+  "colors.onjoin_alert.message":   ("red", "The on-join clone alert's message colour. Formats are space separated."),
+  "colors.onjoin_alert.nick":      ("bold red", "The on-join clone alert's nick colour. Formats are space separated. Note: if you have colorize_nicks, this option might not work as expected."),
+  "colors.onjoin_alert.channel":   ("red", "The on-join clone alert's channel colour. Formats are space separated."),
+  "colors.onjoin_alert.matches":   ("bold red", "The on-join clone alert's matches (masks or nicks) colour. Formats are space separated. Note: if you have colorize_nicks, this option might not work as expected."),
+
+  "colors.join_messages.message":    ("chat", "The base colour for the join messages."),
+  "colors.join_messages.nick":       ("bold", "The colour for the 'nick'-part of the join messages. Note: if you have colorize_nicks, this option might not always work as expected."),
+  "colors.join_messages.identhost":  ("chat", "The colour for the 'ident@host'-part of the join messages."),
+  "colors.join_messages.channel":    ("bold", "The colour for the 'channel'-part of the join messages."),
+
+  "colors.clone_report.header.message":          ("chat", "The colour of the clone report header."),
+  "colors.clone_report.header.number_of_hosts":  ("bold", "The colour of the number of hosts in the clone report header."),
+  "colors.clone_report.header.channel":          ("bold", "The colour of the channel name in the clone report header."),
+
+  "colors.clone_report.subheader.message":        ("chat", "The colour of the clone report subheader."),
+  "colors.clone_report.subheader.host":              ("bold", "The colour of the host in the clone report subheader."),
+  "colors.clone_report.subheader.number_of_clones":  ("bold", "The colour of the number of clones in the clone report subheader."),
+
+  "colors.clone_report.clone.message": ("chat", "The colour of the clone hit in the clone report message."),
+  "colors.clone_report.clone.match":   ("chat", "The colour of the match details (masks or nicks) in the clone report."),
+
+  "colors.mask.nick":      ("bold", "The formatting of the nick in the match mask."),
+  "colors.mask.identhost": ("", "The formatting of the identhost in the match mask."),
+  "hooks.explicit_servers": ("*", "Comma-separated, wildcard-supporting list of servers for which we should add hook to for monitoring clones. E.g. 'freenode,chat4all,esper*' or '*' (default)"),
+  "hooks.excluded_servers": ("bitlbee,twitchtv", "Which servers should the hook *not* be valid for? There's no support for wildcards unfortunately. E.g.: 'bitlbee,twitchtv' to exclude servers named bitlbee and twitchtv (default)."),
+  "lag_warning": ('on', 'Show temporary warning upon script load regarding the inefficiency of the script. Set to "off" to disable.')
+}
+hooks = set([])
+
+def get_validated_key_from_config(setting):
+  key = OPTIONS[setting]
+  if key != 'mask' and key != 'nick':
+    weechat.prnt("", "Key %s not found. Valid settings are 'nick' and 'mask'. Reverted the setting to 'mask'" % key)
+    weechat.config_set_plugin("clone_report_key", "mask")
+    key = "mask"
+  return key
+
+def format_message(msg, formats, reset_color='chat'):
+  if type(formats) == str:
+    formats = formats.split()
+  formatted_message = msg
+  needs_color_reset = False
+  for format in formats:
+    if format in ['bold', 'reverse', 'italic', 'underline']:
+      end_format = '-%s' % format
+    else:
+      needs_color_reset = True
+      end_format = ""
+    formatted_message = "%s%s%s" % (weechat.color(format), formatted_message, weechat.color(end_format))
+  if needs_color_reset:
+    formatted_message += weechat.color(reset_color)
+  return formatted_message
+
+def format_from_config(msg, config_option):
+  return format_message(msg, OPTIONS[config_option])
+
+def on_join_scan_cb(data, signal, signal_data):
+  network = signal.split(',')[0]
+  if network in OPTIONS['hooks.excluded_servers'].split(','):
+    return weechat.WEECHAT_RC_OK
+
+  joined_nick = weechat.info_get("irc_nick_from_host", signal_data)
+  join_match_data = re.match(':[^!]+!([^@]+@(\S+)) JOIN :?([#&]\S*)', signal_data)
+  parsed_ident_host = join_match_data.group(1).lower()
+  parsed_host = join_match_data.group(2).lower()
+  if OPTIONS["compare_idents"] == "on":
+    hostkey = parsed_ident_host
+  else:
+    hostkey = parsed_host
+
+  chan_name = join_match_data.group(3)
+  network_chan_name = "%s.%s" % (network, chan_name)
+  chan_buffer = weechat.info_get("irc_buffer", "%s,%s" % (network, chan_name))
+  if not chan_buffer:
+    print "No IRC channel buffer found for %s" % network_chan_name
+    return weechat.WEECHAT_RC_OK
+
+  if OPTIONS["display_join_messages"] == "on":
+    message = "%s%s%s%s%s" % (
+      format_from_config(joined_nick, "colors.join_messages.nick"),
+      format_from_config("!", "colors.join_messages.message"),
+      format_from_config(parsed_ident_host, "colors.join_messages.identhost"),
+      format_from_config(" JOINed ", "colors.join_messages.message"),
+      format_from_config(network_chan_name, "colors.join_messages.channel"),
+    )
+    #Make sure message format is also applied if no formatting is given for nick
+    message = format_from_config(message, "colors.join_messages.message")
+    weechat.prnt(cs_get_buffer(), message)
+
+  clones = get_clones_for_buffer("%s,%s" % (network, chan_name), hostkey)
+  if clones:
+    key = get_validated_key_from_config("clone_onjoin_alert_key")
+
+    filtered_clones = filter(lambda clone: clone['nick'] != joined_nick, clones[hostkey])
+    match_strings = map(lambda m: format_from_config(m[key], "colors.onjoin_alert.matches"), filtered_clones)
+
+    join_string = format_from_config(' and ',"colors.onjoin_alert.message")
+    masks = join_string.join(match_strings)
+    message = "%s %s %s %s %s" % (
+      format_from_config(joined_nick, "colors.onjoin_alert.nick"),
+      format_from_config("is already on", "colors.onjoin_alert.message"),
+      format_from_config(network_chan_name, "colors.onjoin_alert.channel"),
+      format_from_config("as", "colors.onjoin_alert.message"),
+      masks
+    )
+    message = format_from_config(message, 'colors.onjoin_alert.message')
+
+    if OPTIONS["display_onjoin_alert_clone_buffer"] == "on":
+      weechat.prnt(cs_get_buffer(),message)
+    if OPTIONS["display_onjoin_alert_target_buffer"] == "on":
+      weechat.prnt(chan_buffer, message)
+    if OPTIONS["display_onjoin_alert_current_buffer"] == "on":
+      weechat.prnt(weechat.current_buffer(),message)
+  return weechat.WEECHAT_RC_OK
+
+def cs_get_buffer():
+  global cs_buffer
+
+  if not cs_buffer:
+    # Sets notify to 0 as this buffer does not need to be in hotlist.
+    cs_buffer = weechat.buffer_new("clone_scanner", "", \
+                "", SCRIPT_CLOSE_CB, "")
+    weechat.buffer_set(cs_buffer, "title", "Clone Scanner")
+    weechat.buffer_set(cs_buffer, "notify", "0")
+    weechat.buffer_set(cs_buffer, "nicklist", "0")
+    if OPTIONS["autofocus"] == "on":
+      if not weechat.window_search_with_buffer(cs_buffer):
+        weechat.command("", "/buffer " + weechat.buffer_get_string(cs_buffer,"name"))
+
+  return cs_buffer
+
+def cs_close_cb(*kwargs):
+  """ A callback for buffer closing. """
+  global cs_buffer
+
+  #TODO: Ensure the clone_scanner buffer gets closed if its option is set and the script unloads
+
+  cs_buffer = None
+  return weechat.WEECHAT_RC_OK
+
+
+def get_channel_from_buffer_args(buffer, args):
+  server_name = weechat.buffer_get_string(buffer, "localvar_server")
+  channel_name = args
+  if not channel_name:
+    channel_name = weechat.buffer_get_string(buffer, "localvar_channel")
+
+  match_data = re.match('\A(irc.)?([^.]+)\.([#&]\S*)\Z', channel_name)
+  if match_data:
+    channel_name = match_data.group(3)
+    server_name = match_data.group(2)
+
+  return server_name, channel_name
+
+#TODO: track the hosts + nicks ourselves instead of looking up the entire list every join...
+def get_clones_for_buffer(infolist_buffer_name, hostname_to_match=None):
+  matches = {}
+  infolist = weechat.infolist_get("irc_nick", "", infolist_buffer_name)
+  while(weechat.infolist_next(infolist)):
+    ident_hostname = weechat.infolist_string(infolist, "host")
+    host_matchdata = re.match('([^@]+)@(\S+)', ident_hostname)
+    if not host_matchdata:
+      continue
+
+    hostname = host_matchdata.group(2).lower()
+    ident = host_matchdata.group(1).lower()
+    if OPTIONS["compare_idents"] == "on":
+      hostkey = ident_hostname.lower()
+    else:
+      hostkey = hostname
+
+    if hostname_to_match and hostname_to_match.lower() != hostkey:
+      continue
+
+    nick = weechat.infolist_string(infolist, "name")
+
+    matches.setdefault(hostkey,[]).append({
+      'nick': nick,
+      'mask': "%s!%s" % (
+        format_from_config(nick, "colors.mask.nick"),
+        format_from_config(ident_hostname, "colors.mask.identhost")),
+      'ident': ident,
+      'ident_hostname': ident_hostname,
+      'hostname': hostname,
+    })
+  weechat.infolist_free(infolist)
+
+  #Select only the results that have more than 1 match for a host
+  return dict((k, v) for (k, v) in matches.iteritems() if len(v) > 1)
+
+def report_clones(clones, scanned_buffer_name, target_buffer=None):
+  # Default to clone_scanner buffer
+  if not target_buffer:
+    target_buffer = cs_get_buffer()
+
+  if clones:
+    clone_report_header = "%s %s %s%s" % (
+      format_from_config(len(clones), "colors.clone_report.header.number_of_hosts"),
+      format_from_config("hosts with clones were found on", "colors.clone_report.header.message"),
+      format_from_config(scanned_buffer_name, "colors.clone_report.header.channel"),
+      format_from_config(":", "colors.clone_report.header.message"),
+    )
+    clone_report_header = format_from_config(clone_report_header, "colors.clone_report.header.message")
+    weechat.prnt(target_buffer, clone_report_header)
+
+    for (host, clones) in clones.iteritems():
+      host_message = "%s %s %s %s" % (
+        format_from_config(host, "colors.clone_report.subheader.host"),
+        format_from_config("is online from", "colors.clone_report.subheader.message"),
+        format_from_config(len(clones), "colors.clone_report.subheader.number_of_clones"),
+        format_from_config("nicks:", "colors.clone_report.subheader.message"),
+      )
+      host_message = format_from_config(host_message, "colors.clone_report.subheader.message")
+      weechat.prnt(target_buffer, host_message)
+
+      for user in clones:
+        key = get_validated_key_from_config("clone_report_key")
+        clone_message = "%s%s" % (" - ", format_from_config(user[key], "colors.clone_report.clone.match"))
+        clone_message = format_from_config(clone_message,"colors.clone_report.clone.message")
+        weechat.prnt(target_buffer, clone_message)
+  else:
+    weechat.prnt(target_buffer, "No clones found on %s" % scanned_buffer_name)
+
+def cs_command_main(data, buffer, args):
+  if args[0:4] == 'scan':
+    server_name, channel_name = get_channel_from_buffer_args(buffer, args[5:])
+    clones = get_clones_for_buffer('%s,%s' % (server_name, channel_name))
+    if OPTIONS["display_scan_report_target_buffer"] == "on":
+      target_buffer = weechat.info_get("irc_buffer", "%s,%s" % (server_name, channel_name))
+      report_clones(clones, '%s.%s' % (server_name, channel_name), target_buffer)
+    if OPTIONS["display_scan_report_clone_buffer"] == "on":
+      report_clones(clones, '%s.%s' % (server_name, channel_name))
+    if OPTIONS["display_scan_report_current_buffer"] == "on":
+      report_clones(clones, '%s.%s' % (server_name, channel_name), weechat.current_buffer())
+  elif args[0:9] == 'advertise':
+    weechat.command("", "/input insert /me is using FiXato's CloneScanner v%s for WeeChat. Get the latest version from: https://github.com/FiXato/weechat_scripts/blob/master/clone_scanner.py or /script install clone_scanner.py" % SCRIPT_VERSION)
+  return weechat.WEECHAT_RC_OK
+
+def cs_set_default_settings():
+  global OPTIONS
+
+  # Set default settings
+  for option,value in OPTIONS.items():
+    if not weechat.config_is_set_plugin(option):
+        weechat.config_set_plugin(option, value[0])
+        OPTIONS[option] = value[0]
+    else:
+        OPTIONS[option] = weechat.config_get_plugin(option)
+    weechat.config_set_desc_plugin(option, '%s (default: "%s")' % (value[1], value[0]))
+
+def toggle_refresh(pointer, name, value):
+  global OPTIONS
+
+  config_option          = name[len('plugins.var.python.' + SCRIPT_NAME + '.'):]  # get optionname
+  OPTIONS[config_option] = value                                                  # save new value
+  if config_option in ('hooks.excluded_servers', 'hooks.explicit_servers'):