# "Bldjid" plugin for Tkabber. 2010-06-27 v. 0.9.11.12
# Written by Serge Yudin xmpp:bigote@jabber.ru
# See README or README.ru file for usage.

namespace eval bldjid {
	::msgcat::mcload [file join [file dirname [info script]] msgs]
    if {![::plugins::is_registered bldjid]} {
        ::plugins::register bldjid \
                -namespace [namespace current] \
                -source [info script] \
                -description [::msgcat::mc "Whether the Bldjid plugin is loaded."] \
                -loadcommand [namespace code load] \
                -unloadcommand [namespace code unload]
        return
    }

		set winid 0

# Set plugin options.
		set verbosity [list \
		min [::msgcat::mc "Minimum:\
		Log each entrance or nick change of a unique pair nick/jid only once per room\
		(less memory used)."]\
		max [::msgcat::mc "Maximum: All entrances of each user will be logged."]]

		set matching_rules [list \
		exact [::msgcat::mc "Exact match."]\
		loose [::msgcat::mc "Loose match."]]

		custom::defgroup Plugins [::msgcat::mc "Plugins options."] -group Tkabber

		custom::defgroup Bldjid [::msgcat::mc "Bldjid options."] -group Plugins

		custom::defvar options(enable_jids_gathering) 1 \
			[::msgcat::mc "Enable JIDs logging.\
			Disabling it doesn't prevent /bldjid-like commands to work."] \
			-type boolean -group Bldjid

		custom::defvar options(rooms2log) "" \
			[::msgcat::mc "A list of conferences (full jids separated with spaces)\
			that should be logged. If empty, all rooms will be logged."] \
			-type string -group Bldjid

		custom::defvar options(log_file) [file join $::configdir bldjid.log] \
			[::msgcat::mc "Bldjid log file. If empty, logs won't be saved to file."] \
			-group Bldjid \
			-type file

		custom::defvar options(filter_admin_rooms) 1 \
			[::msgcat::mc "This option doesn't prevent logging\
			in the rooms where you don't have admin privileges\
			but it helps in not showing such results."] \
			-type boolean -group Bldjid

		custom::defvar options(strip_resource) 0 \
			[::msgcat::mc "You can choose here if JIDs should be logged stripped\
			(without resources) or not (with resources).\
			They are not stripped by default as it's more informative."] \
			-type boolean -group Bldjid

		custom::defvar options(log_role_aff) 0 \
			[::msgcat::mc "Enable this option to log visitors' affiliation and role."] \
			-type boolean -group Bldjid

		custom::defvar options(log_ver_os) 0 \
			[::msgcat::mc "Enable this option to log visitors' client version and OS."] \
			-type boolean -group Bldjid

		custom::defvar options(log_ver_hash) 0 \
			[::msgcat::mc "Enable this option to log visitors' client version hash\
			(see XEP-0115: Entity Capabilities for details)."] \
			-type boolean -group Bldjid

		custom::defvar options(ver_timeout) 120 \
			[::msgcat::mc "Timeout for version requests (in seconds).\
			See README for details."] \
			-type integer -group Bldjid

		custom::defvar options(verbosity_level) min \
			[::msgcat::mc "Verbosity level."] \
			-type radio -group Bldjid -layout vertical -values $verbosity

		custom::defvar options(matching_rules) loose \
			[::msgcat::mc "Matching rules for /visit* commands."] \
			-type radio -group Bldjid -layout vertical -values $matching_rules

		custom::defvar options(custom_separator) "||" \
			[::msgcat::mc "Field separator for /visit* commands."] \
			-type string -group Bldjid

		custom::defvar options(redirect_output) 0 \
			[::msgcat::mc "Redirect output of /visit* and /bldhelp commands\
			to a special monitor window. Use <Ctrl-m> to toggle between monitor states."] \
			-type boolean -group Bldjid

		custom::defvar options(send_chat_messages) 0 \
			[::msgcat::mc "Generate and send to chats smart system messages\
			about user entrances (will only work with Minimum verbosity level)."] \
			-type boolean -group Bldjid

		custom::defvar options(log_length) 0 \
			[::msgcat::mc "Max length of your JID list for each room.\
			Zero means unlimited. See README for details."] \
			-type integer -group Bldjid

		custom::defvar options(amoder) "" \
			[::msgcat::mc "Automoderator list (of JIDs).\
			A regular expression is expected here."] \
			-type string -group Bldjid

		custom::defvar options(amoder_groups) "" \
			[::msgcat::mc "A space separated list of rooms where automoderator\
			list is to be applied. Empty field means all rooms."] \
			-type string -group Bldjid

		custom::defvar options(watch_groups) "" \
			[::msgcat::mc "A space separated list of rooms for watching\
			with /watch command. Empty field means all rooms."] \
			-type string -group Bldjid

}

proc bldjid::load {} {
# A variable where the last query result is stored.
	variable user_list
	variable watched_jids
	variable watch_id 0
	variable jidver
	variable jidnodes
# An array where all user entrances are stored.
	global jids_by_chats
	variable ent_cntr 1
	variable id 1
	event add <<ToggleMonitor>> <Control-m>

	hook::add generate_completions_hook \
	[namespace current]::visitors_compls
	hook::add chat_send_message_hook \
	[namespace current]::handle_commands 17
	::hook::add open_chat_post_hook [namespace current]::setup_bindings
	hook::add chat_user_enter \
	[namespace current]::smart_enter_exit_message 69
	hook::add room_nickname_changed_hook \
	[namespace current]::bridge2_smart_enter_exit_message 100
	hook::add client_presence_hook \
	[namespace current]::caps_collector 100
}

proc bldjid::unload {} {
	variable user_list
	variable watched_jids
	variable watch_id
	global jids_by_chats
	variable jidver
	variable jidnodes
	variable ent_cntr
	variable id
	event delete <<ToggleMonitor>> <Control-m>

	hook::remove generate_completions_hook \
	[namespace current]::visitors_compls
	hook::remove chat_send_message_hook \
	[namespace current]::handle_commands 17
	::hook::remove open_chat_post_hook [namespace current]::setup_bindings
	hook::remove chat_user_enter \
	[namespace current]::smart_enter_exit_message 69
	hook::remove room_nickname_changed_hook \
	[namespace current]::bridge2_smart_enter_exit_message 100
	hook::remove client_presence_hook \
	[namespace current]::caps_collector 100

	catch {unset ent_cntr}
	catch {unset id}
	catch {unset user_list}
	catch {unset jids_by_chats}
	catch {unset ver_os}
	catch {unset watched_jids}
	catch {unset watch_id}
	catch {unset jidver}
	catch {unset jidnodes}

	foreach chatid [chat::opened] {
		clear_monitor $chatid
		set ccw [::chat::winid $chatid]
		if {![catch {pack info $ccw.mon} res]} {
			pack forget $ccw.mon $ccw.vsb
		}
	}
}

proc bldjid::setup_bindings {chatid type} {
	set w [::chat::input_win $chatid]
    bind $w <<ToggleMonitor>> [list [namespace current]::toggle_monitor $chatid 1]
    bind $w <<ToggleMonitor>> +break
}

proc bldjid::bridge2_smart_enter_exit_message {chatid nick newnick} {
	after idle [namespace current]::smart_enter_exit_message [list $chatid $newnick]
}

## Thanks to Alexey Smirnov <alexey.smirnov@gmx.com>
## and Rakhmanin Ruslan <rakhmaninr@gmail.com>
## Adapted to bldjid's necessities by Serge Yudin
proc bldjid::smart_enter_exit_message {chatid nick} {
	variable ent_cntr
	variable id
	variable watched_jids
	global jids_by_chats
	variable jidver
	variable jidnodes
	variable options
	if {!$options(enable_jids_gathering)} {
		return
	}
	set group [::chat::get_jid $chatid]
	if {$options(rooms2log) != "" && ![string match *$group* $options(rooms2log)]} {
		return
	}
	if {[chat::is_groupchat $chatid]} {
		set user_already_here [lcontain [::muc::roster $chatid] $nick]
		if {!$user_already_here} {
			return
		}
	} else {
		return
	}
	if {[::chat::is_opened $chatid]} {
		if {[::chat::is_groupchat $chatid]} {
			if {$nick == ""} {
				return
			}
			set xlib [::chat::get_xlib $chatid]
			set ts [clock format [clock seconds] \
				-format {%Y-%m-%d_%H:%M:%S}]
			set real_jid [::muc::get_real_jid $xlib $group/$nick]
			set bare_jid [::xmpp::jid::stripResource $real_jid]
			if {$options(strip_resource)} {
				set real_jid $bare_jid
			}
			if {[info exists jids_by_chats($group)]} {
				if {$options(verbosity_level) == "min"} {
					foreach item $jids_by_chats($group) {
						if {[lindex $item 2] == $real_jid
						&&  [lindex $item 1] == $nick} {
							return
						}
					}
				}
			}
			if {$options(log_length) != "" \
			&& [[namespace current]::is_any_integer $options(log_length)] \
			&& $options(log_length) > 0 \
			&& [info exists jids_by_chats($group)]\
			&& [llength $jids_by_chats($group)] >= $options(log_length)} {
				set jids_by_chats($group) [lreplace $jids_by_chats($group) 0 \
				[expr {[llength $jids_by_chats($group)] \
				- $options(log_length)}]]
			}
			if {$options(log_role_aff)} {
				set role_aff [::muc::get_role $xlib $group/$nick]
				append role_aff "/" [::muc::get_affiliation $xlib $group/$nick]
			} else {
				set role_aff ""
			}
			if {[info exists jidver($nick,$chatid)]} {
				set ver_hash $jidver($nick,$chatid)
			} else {
				set ver_hash ""
			}
			if {[info exists jidnodes($nick,$chatid)]} {
				set node $jidnodes($nick,$chatid)
			} else {
				set node ""
			}
			if {[info exists watched_jids]} {
				set iam [whoami $xlib $group]
				if {[info exists watched_jids(nick)]} {
					set pattern $nick
					if {[string first $pattern [join $watched_jids(nick)]] >= 0 \
					&& ![string match *%*@irc* $group] \
					&& ([string match *$group* $options(watch_groups)] \
					||  $options(watch_groups) == "")} {
						[namespace current]::process_watch \
						$pattern $group $nick $bare_jid $role_aff $chatid \
						$watched_jids(action) $iam
					}
				}
				if {[info exists watched_jids(jid)]} {
					set pattern $watched_jids(jid)
					if {[regexp -nocase $watched_jids(jid) $bare_jid] \
					&&  [string match *moderator* $iam] \
					&& ![string match *%*@irc* $group] \
					&& ([string match *$group* $options(watch_groups)] \
					||  $options(watch_groups) == "")} {
						[namespace current]::process_watch \
						$pattern $group $nick $bare_jid $role_aff $chatid \
						$watched_jids(action) $iam
					}
				}
				if {[info exists watched_jids(hash)] && $ver_hash != ""} {
					set pattern [string map {\{ \\\{ \} \\\} \/ \\\/ \= \\\=} $ver_hash]
					if {[string match *$pattern* [join $watched_jids(hash)]] \
					&& ![string match *%*@irc* $group] \
					&& ([string match *$group* $options(watch_groups)] \
					||  $options(watch_groups) == "")} {
						[namespace current]::process_watch \
						$pattern $group $nick $bare_jid $role_aff $chatid \
						$watched_jids(action) $iam
					}
				}
			}
			if {$options(send_chat_messages) && $options(verbosity_level) == "min"} {
				set occupant "$nick ($real_jid)"
				set msg [::msgcat::mc "%s has entered" $occupant]
				::chat::add_message $chatid $group groupchat $msg {}
			}
			if {!$options(log_ver_os)} {
				set ver_os ""
				lappend jids_by_chats($group) \
				[list $ts $nick $real_jid $role_aff $ver_os $ver_hash $id]
				if {$options(log_file) != ""} {
					set msg4log \
					"$ent_cntr\t$ts\t$group\t$nick\t$real_jid\t$role_aff\t$ver_os\t$ver_hash"
					[namespace current]::log_msg $msg4log
					incr ent_cntr
				}
			} else {
				set ver_os "Timeout"
				lappend jids_by_chats($group) \
				[list $ts $nick $real_jid $role_aff $ver_os $ver_hash $id]
				if {[[namespace current]::is_any_integer $options(ver_timeout)]} {
					set vto [expr $options(ver_timeout) * 1000]
				} else {
					set vto 120000
				}
				set iam [whoami $xlib $group]
				set pid [after $vto \
				[namespace current]::process_ver \
				[list $chatid $nick $ts $group $real_jid $role_aff $ver_hash $node $iam $id]]
				[namespace current]::request_iq \
				$xlib $chatid $group $nick $ts $real_jid $role_aff $ver_hash $node $pid $id
			}
			incr id
			if {$options(amoder) != ""} {
				if {($options(amoder_groups) == "" \
				||  [string match *$group* $options(amoder_groups)])} {
					set usr_role [::muc::get_role $xlib $group/$nick]
					if {$usr_role != "moderator" \
					&& [regexp -nocase $options(amoder) $real_jid]} {
						after idle [namespace current]::amoder \
						[list $xlib $group $nick $chatid]
					}
				}
			}
		}
	}
}

## Thanks to Alexey Smirnov <alexey.smirnov@gmx.com>
## Adapted by Serge Yudin.
proc bldjid::visitors_compls {chatid compsvar wordstart line} {
	variable user_list
	set group [::xmpp::jid::stripResource [chat::get_jid $chatid]]
	if {![::muc::is_compatible $group] || ![::chat::is_groupchat $chatid]} return

	upvar 0 $compsvar comps

	if {!$wordstart} {
		lappend comps {/bldjid } {/unbldjid } {/inbanlist } {/inmemberlist } \
		{/amnesty } {/visitors } {/visited } {/bldhelp } {/clearmon } \
		{/watch } {/unwatch } {/listwatch } {/massban } {/captcha } {/monly }
	}

	if {$wordstart && [regexp {^/(bldjid|unban|unbldjid).*} $line]} {
		set jidcomps {}
		if {[info exists user_list]} {
			foreach jid $user_list {
				if {$jid != ""} {
					lappend jidcomps [lindex $jid 2]
				}
			}
			set jidcomps [lsort -dictionary -unique $jidcomps]
			set comps [concat $jidcomps $comps]
		} else {
			return stop
		}
	}
}

proc bldjid::handle_commands {chatid user body type} {
	set no_jid [::msgcat::mc "I can't see any JID of this user."]
	variable watched_jids

# Detect the command.
	if {[string match {/bldjid*} $body]} {
		set cmd /bldjid
	} elseif {[string match {/massban*} $body]} {
		set cmd /massban
	} elseif {[string match {/inbanlist*} $body]} {
		set cmd /inbanlist
	} elseif {[string match {/inmemberlist*} $body]} {
		set cmd /inmemberlist
	} elseif {[string match {/unbldjid*} $body]} {
		set cmd /unbldjid
	} elseif {[string match {/amnesty*} $body]} {
		set cmd /amnesty
	} elseif {[string match {/visitors*} $body]} {
		set cmd /visitors
	} elseif {[string match {/visited*} $body]} {
		set cmd /visited
	} elseif {[string match {/bldhelp*} $body]} {
		set cmd /bldhelp
	} elseif {[string match {/clearmon*} $body]} {
		set cmd /clearmon
	} elseif {[string match {/watch*} $body]} {
		set cmd /watch
	} elseif {[string match {/unwatch*} $body]} {
		set cmd /unwatch
	} elseif {[string match {/listwatch*} $body]} {
		set cmd /listwatch
	} elseif {[string match {/captcha*} $body]} {
		set cmd captcha_protected
	} elseif {[string match {/monly*} $body]} {
		set cmd muc#roomconfig_membersonly
	} else return

	if {$type != "groupchat"} return

	set group [chat::get_jid $chatid]
	set room ""
	set nick ""
	set state ""

	if {[string equal [string range $body 0 9] "/unbldjid "]} {
		set jid [::plugins::muc::parse_nick $body 10]
	} elseif {[string equal [string range $body 0 10] "/inbanlist "]} {
		set jid [::plugins::muc::parse_nick $body 11]
		set attr affiliation
		set val outcast
	} elseif {[string equal [string range $body 0 13] "/inmemberlist "]} {
		set jid [::plugins::muc::parse_nick $body 14]
		set attr affiliation
		set val member
	} elseif {[string equal [string range $body 0 7] "/bldjid "]} {
		lassign [::plugins::muc::parse_nick_reason $body 8] jid reason
		set params {affiliation outcast}
	} elseif {[string equal [string range $body 0 8] "/massban "]} {
		lassign [::plugins::muc::parse_nick_reason $body 9] jidlist reason
		set params {affiliation outcast}
	} elseif {[string equal [string range $body 0 7] "/amnesty"]} {
		set reason [::plugins::muc::parse_nick $body 8]
		set attr affiliation
		set val outcast
	} elseif {[string equal [string range $body 0 8] "/visitors"]} {
		if {[string index $body 9] == " " && [string length $body] > 10} {
			set room [::plugins::muc::parse_nick $body 10]
		}
	} elseif {[string equal [string range $body 0 7] "/visited"]} {
		if {[string index $body 8] == " " && [string length $body] > 9} {
			lassign [::plugins::muc::parse_nick_reason $body 9] nick room
		}
	} elseif {[string equal [string range $body 0 7] "/bldhelp"]} {
		[namespace current]::help $chatid $group
	} elseif {[string equal [string range $body 0 8] "/clearmon"]} {
		[namespace current]::clear_monitor $chatid
	} elseif {[string equal [string range $body 0 5] "/watch"]} {
		if {[string index $body 6] == " " && [string length $body] > 7} {
			lassign [::plugins::muc::parse_nick_reason $body 7] jid action
		}
	} elseif {[string equal [string range $body 0 8] "/unwatch "]} {
		set jid [::plugins::muc::parse_nick $body 9]
	} elseif {[string equal [string range $body 0 9] "/listwatch"]} {
		[namespace current]::listwatch $chatid $group
	} elseif {[string equal [string range $body 0 8] "/captcha "]} {
		set state [::plugins::muc::parse_nick $body 9]
		[namespace current]::request_config $chatid $state $cmd
	} elseif {[string equal [string range $body 0 7] "/captcha"]} {
		[namespace current]::request_config $chatid $state $cmd
	} elseif {[string equal [string range $body 0 6] "/monly "]} {
		set state [::plugins::muc::parse_nick $body 7]
		[namespace current]::request_config $chatid $state $cmd
	} elseif {[string equal [string range $body 0 5] "/monly"]} {
		[namespace current]::request_config $chatid $state $cmd
	} else {
		return stop
	}

	variable user_list
	set xlib [chat::get_xlib $chatid]

	switch -- $cmd {
		/unbldjid {
			set groupjids [[namespace current]::valid_groups $xlib admin {}]
			foreach gr $groupjids {
				muc::unban $xlib $gr $jid
			}
			set ts [clock format [clock seconds] \
			-format $::plugins::options(timestamp_format)]
            set msg4log "$ts   User $jid is UNbanned in room(s) $groupjids."
		    [namespace current]::log_msg $msg4log
		}

		/bldjid {
			set groupjids [[namespace current]::valid_groups $xlib admin {}]
			if {[string is integer $jid] && [info exists user_list]} {
				set jid [[namespace current]::pickup $jid $chatid]
			}
			if {$jid != ""} {
				foreach gr $groupjids {
					[namespace current]::send_ban_request \
						$xlib $gr $params $jid $reason
				}
			    set ts [clock format [clock seconds] \
			    -format $::plugins::options(timestamp_format)]
                set msg4log "$ts   User $jid is banned in room(s) $groupjids for reason \"$reason\"."
		        [namespace current]::log_msg $msg4log
			} else {
				chat::add_message $chatid $group error $no_jid {}
			}
		}

		/massban {
			foreach jid $jidlist {
				::xmpp::muc::setAffiliation $xlib $group outcast \
					-jid $jid -reason $reason \
					-command [list muc::test_error_res \
					"affiliation outcast '$jid'" \
					$xlib $group $chatid]
			}
			return stop
		}

		/amnesty {
			variable winid
			set w .amn$winid
			incr winid
			if {[winfo exists $w]} {
				destroy $w
			}
			if {$reason == ""} {
				set say [::msgcat::mc "Are you sure you want to unban all outcasts in this room?\n\
				There will be no backup: you're either kind-hearted or not :)"]
			} else {
				set say [::msgcat::mc "Are you sure you want to unban people in this room\n\
				whose ban reason matches '%s'?\n\
				There will be no backup: you're either kind-hearted or not :)" [string trim $reason]]
			}
			Dialog $w -title [::msgcat::mc "Forgive all outcasts here?"] \
				-modal none -separator 1 -anchor e -default 0 -cancel 1 \
				-parent .
				set f [$w getframe]
				$w add -text [::msgcat::mc "OK"] \
				-command [list [namespace current]::amnesty \
				$chatid $attr $val $w $f $reason]
				$w add -text [::msgcat::mc "Cancel"] -command [list destroy $w]

				label $w.lsure -padx 10 -pady 5 \
				-text $say
				pack $w.lsure -expand yes -fill both
				$w draw
		}

		/visitors {
			[namespace current]::visitors $xlib $chatid $room $group
		}

		/visited {
			[namespace current]::visited $xlib $chatid $room $group $nick
		}

		/inbanlist {
			[namespace current]::request_list $attr $val $chatid \
			[list inbanlist $jid]
		}

		/inmemberlist {
			[namespace current]::request_list $attr $val $chatid \
			[list inmemberlist $jid]
		}

		/watch {
			set cmd "/watch"
			set msg [::msgcat::mc \
			"Wrong syntax. Should be \"%s keyword parameter\".\
			\nValid keywords are: nick, jid, hash, ver." $cmd]
			set key ""
			if {[info exists jid]} {
				set jid [split $jid \ ]
				set key [lindex $jid 0]
				set jid [lreplace $jid 0 0]
			} else {
				::chat::add_message $chatid $group error $msg {}
				return stop
			}
			if {$key != "nick" && $key != "hash" && $key != "ver" && $key != "jid"} {
				set group [::chat::get_jid $chatid]
				::chat::add_message $chatid $group error $msg {}
			}
			if {$key == "hash"} {
				foreach j $jid {
					lappend watched_jids($key) $j
				}
			} else {
				if {[info exists watched_jids($key)]} {
					unset watched_jids($key)
				}
				set watched_jids($key) [join $jid]
			}
			if {[info exists action] && $action != ""} {
				set act [lindex $action 0]
				if {$act != "devoice" && $act != "kick" && $act != "ban" && $act != "say"} {
					chat::add_message $chatid $room error \
					[::msgcat::mc "Wrong action \'%s\' was specified.\
					It should be one of the following: say, devoice, kick or ban.\
					If no action is given you'll receive a notification." $act] {}
					return stop
				} else {
					set watched_jids(action) $action
				}
			} else {
				set watched_jids(action) ""
			}
		}

		/unwatch {
			if {[info exists watched_jids]} {
				if {$jid == "all"} {
					catch {unset watched_jids}
				} else {
					set key [lindex $jid 0]
					if {$key == "ver" && [info exists watched_jids(ver)]} {
						catch {unset watched_jids(ver)}
						return stop
					} elseif {$key == "nick" || $key == "jid" || $key == "hash"} {
						set jid [lindex $jid 1]
					} else {
						set group [::chat::get_jid $chatid]
						set cmd "/unwatch"
						set msg [::msgcat::mc \
						"Wrong syntax. Should be \"%s keyword parameter\".\
						\nValid keywords are: nick, jid, hash, ver." $cmd]
						::chat::add_message $chatid $group error $msg {}
						return stop
					}
					if {$jid == "all"} {
						catch {unset watched_jids($key)}
					} elseif {[info exists watched_jids($key)]} {
						for {set i 0} {$i < [llength $watched_jids($key)]} {incr i} {
							if {[string equal $jid \
							[join [lindex $watched_jids($key) $i]]]} {
								set watched_jids($key) [lreplace $watched_jids($key) $i $i]
								if {[llength $watched_jids($key)] == 0} {
									catch {unset watched_jids($key)}
								}
								break
							}
						}
					} else {
						set group [::chat::get_jid $chatid]
						set cmd "/unwatch"
						set msg [::msgcat::mc \
						"Wrong syntax. Should be \"%s keyword parameter\".\
						\nValid keywords are: nick, jid, hash, ver." $cmd]
						::chat::add_message $chatid $group error $msg {}
					}
				}
			}
		}
	}
	return stop
}

proc bldjid::listwatch {chatid group} {
	variable watched_jids
	set header [::msgcat::mc "These are items you are watching: "]
	set msg ""
	if {[info exists watched_jids]} {
		foreach key {nick jid hash ver action} {
			if {[info exists watched_jids($key)]} {
				lappend msg "\n$key: $watched_jids($key)"
			}
		}
	}
	if {$msg == ""} {
		set msg [::msgcat::mc "Your watchlist is empty."]
	}
	[namespace current]::print_search_res $chatid $group $header $msg
}

proc bldjid::send_ban_request {xlib group params jid reason} {
	set itemsubtags {}
	if {$reason != ""} {
		lappend itemsubtags [::xmpp::xml::create reason -cdata $reason]
	}
	set vars [list jid $jid]
	set item [::xmpp::xml::create item \
		-attrs [concat $vars $params] \
		-subelements $itemsubtags]
		::xmpp::sendIQ $xlib set \
		-query [::xmpp::xml::create query \
		-xmlns "http://jabber.org/protocol/muc#admin" \
		-subelement $item] \
		-to $group \
		-command [list [namespace current]::test_error_res "$params \'$jid\'" $xlib $group]
}

proc bldjid::test_error_res {op xlib group res child} {
	set chatid [chat::chatid $xlib $group]
	if {![string equal $res ok]} {
		chat::add_message $chatid $group error \
		[format "%s: %s" $op [error_to_string $child]] {}
	} else {
		chat::add_message $chatid $group info \
		[format "%s: %s" $op "User is banned"] {}
	}
	return
}

proc bldjid::amnesty {chatid attr val w f reason} {
	set reason [linsert $reason 0 amnesty]
	[namespace current]::request_list $attr $val $chatid $reason
	destroy $w
}

proc bldjid::request_list {attr val chatid cmd} {
	set xlib [chat::get_xlib $chatid]
	set group [chat::get_jid $chatid]
	set comm [lindex $cmd 0]
	if {$comm == "amnesty"} {
			set cmd [list [namespace current]::receive_send_list \
			$attr $val $chatid $cmd]
	} elseif {$comm == "inbanlist" || $comm == "inmemberlist"} {
		set jid ""
		if {[llength $cmd] > 1} {
			set jid [string tolower [lindex $cmd 1]]
		}
		set cmd [list [namespace current]::receive_search_list \
			$attr $val $chatid $jid]
	} else {
		return
	}
	::xmpp::sendIQ $xlib get \
		-query [::xmpp::xml::create query \
		-xmlns "http://jabber.org/protocol/muc#admin" \
		-subelement [::xmpp::xml::create item \
		-attrs [list $attr $val]]] \
		-to $group \
		-command $cmd
}

proc bldjid::receive_search_list {attr val chatid test_jid res child} {
	variable options
	set xlib [chat::get_xlib $chatid]
	set group [chat::get_jid $chatid]
	if {![string equal $res ok]} {
		chat::add_message $chatid $group error \
		"$attr $val list: [error_to_string $child]" {}
		return
	}
	::xmpp::xml::split $child tag xmlns attrs cdata items
	if {$items == ""} {
		chat::add_message $chatid $group error \
		[::msgcat::mc "The %s list is empty for %s." $val $group] {}
		return
	}
	set found 0
	switch -- $options(matching_rules) {
		loose {
			set header [::msgcat::mc \
			"JIDs that match your search pattern '%s' in %s list of this room:" \
			$test_jid $val]
		}
		exact {
			set header [::msgcat::mc \
			"Search for JID '%s' in %s list of this room:" \
			$test_jid $val]
		}
	}
	set msg ""
	if {[llength $test_jid] == 2 && [lindex $test_jid 0] == "!"} {
		set test_jid [lindex $test_jid 1]
		set ignore 1
	} elseif {[llength $test_jid] == 1} {
		set ignore 0
	} else {
		return
	}
	foreach item $items {
		::xmpp::xml::split $item tag xmlns attrs cdata subels
		switch -- $tag {
			item {
				set jid [::xmpp::xml::getAttr $attrs jid]
			}
		}
		set reason [lindex [lindex $subels 0] end-1]
		if {$reason != ""} {
			set reason " -- $reason"
		}
		switch -- $options(matching_rules) {
			loose {
				if {!$ignore && [string match *$test_jid* $jid]} {
					lappend msg "\n$jid$reason"
					set found 1
				} elseif {$ignore && ![string match *$test_jid* $jid]} {
					lappend msg "\n$jid$reason"
					set found 1
				}
			}
			exact {
				if {!$ignore && [string equal $test_jid $jid]} {
					lappend msg "\n$jid$reason"
					set found 1
					break
				} elseif {$ignore && ![string equal $test_jid $jid]} {
					lappend msg "\n$jid$reason"
					set found 1
					break
				}
			}
		}
	}
	if {!$found} {
		append header [::msgcat::mc "\nNo matches were found."]
	}
	[namespace current]::print_search_res $chatid $group $header $msg
}

proc bldjid::receive_send_list {attr val chatid cmd res child} {
	set xlib [chat::get_xlib $chatid]
	set group [chat::get_jid $chatid]
	if {![string equal $res ok]} {
		chat::add_message $chatid $group error \
		"$attr $val list: [error_to_string $child]" {}
		return
	}
	set rsn ""
	if {[llength $cmd] > 1} {
		set rsn [lreplace $cmd 0 0]
	}
	::xmpp::xml::split $child tag xmlns attrs cdata items
	if {$items == {}} {
		chat::add_message $chatid $group error \
		[::msgcat::mc "The blacklist is empty for %s." $group] {}
		return
	}
	set items2 {}
	set i 0
	foreach item $items {
		::xmpp::xml::split $item tag xmlns attrs cdata subels
		switch -- $tag {
			item {
				set nick [::xmpp::xml::getAttr $attrs nick]
				set jid [::xmpp::xml::getAttr $attrs jid]
				set role [::xmpp::xml::getAttr $attrs role]
				set affiliation [::xmpp::xml::getAttr $attrs affiliation]
				set reason ""
				foreach subitem $subels {
					::xmpp::xml::split $subitem stag sxmlns sattrs scdata ssubels
					if {$stag == "reason"} {
						set reason $scdata
					}
				}
				set itemsubtags {}
				lappend itemsubtags [::xmpp::xml::create reason \
					-cdata $reason]
				if {$rsn != "" && [string match -nocase *$rsn* $reason]} {
					if {[string match \{*\}* $jid]} {
						set jid [string map {\{ \\\{ \} \\\}} $jid]
					}
					lappend items2 [::xmpp::xml::create item \
						-attrs "affiliation none jid $jid" \
						-subelements $itemsubtags]
# Checking if we already have 700 jids prepared. You can try to set more
# but I got Stream error when sending 1300 jids. The limit is in between :)
					incr i
					if {$i > 700} {
						break
					}
				} elseif {$rsn == ""} {
					lappend items2 [::xmpp::xml::create item \
						-attrs "affiliation none jid $jid" \
						-subelements $itemsubtags]
					incr i
					if {$i > 700} {
						break
					}
				}
			}
		}
	}
	if {$items2 != {}} {
		::xmpp::sendIQ $xlib set \
			-query [::xmpp::xml::create query \
			-xmlns "http://jabber.org/protocol/muc#admin" \
			-subelements $items2] \
			-to $group \
			-command [list muc::test_error_res \
			[::msgcat::mc "Sending %s %s list" $attr $val] \
			$xlib $group $chatid]
	}
	set ts [clock format [clock seconds] \
		-format $::plugins::options(timestamp_format)]
	if {$rsn == ""} {
		set postamnesty [::msgcat::mc \
			"Thank you for your generosity! +10 to Reputation!"]
		set msg4log "$ts   All users are succesfully UNbanned in the room $group."
	} else {
		set postamnesty [::msgcat::mc \
			"Thank you for your generosity! +1 to Reputation!"]
		set msg4log "$ts   Users whose ban reason matched '$rsn' \
			are succesfully UNbanned in the room $group."
	}
	chat::add_message $chatid $group info $postamnesty {}
	[namespace current]::log_msg $msg4log
}

# Here we get our role and affiliation in a room.
# Thanks to Konstantin Khomoutov <kostix@007spb.ru>
proc bldjid::whoami {xlib where} {
	if {[muc::is_compatible $where]} {
		set chatid [::chat::chatid $xlib $where]
		set jid $where/[get_our_groupchat_nick $chatid]
		return [::muc::get_role $xlib $jid]/[muc::get_affiliation $xlib $jid]
	}
}

proc bldjid::valid_groups {xlib which pattern} {
# Filter out wrong conferences.
# We assume IRC-channels have to have "irc" part in their transport name.
# If some transport doesn't have it (1% of probability),
# a ban request will be sent there and we'll get an error.
	variable options
	set grpjids ""
	foreach tmpchatid [lsort [lfilter chat::is_groupchat [chat::opened $xlib]]] {
		set tmpgrp [::chat::get_jid $tmpchatid]
		if {$pattern != ""} {
			switch -- $options(matching_rules) {
				exact {
					if {[string equal $tmpgrp $pattern]} {
						lappend grplist $tmpchatid
					}
				}
				loose {
					if {[string match *$pattern* $tmpgrp]} {
						lappend grplist $tmpchatid
					}
				}
			}
		} else {
			lappend grplist $tmpchatid
		}
	}
	if {![info exists grplist]} {
		return stop
	}
	foreach tmpchatid $grplist {
		set tmpgrp [::chat::get_jid $tmpchatid]
		set nick [get_our_groupchat_nick $tmpchatid]
		if {[lcontain [::muc::roster $tmpchatid] $nick]} {
			switch -- $which {
				admin {
					set iam [whoami $xlib $tmpgrp]
					if {[string match *moderator* $iam] \
						&& ![string match *%*@irc* $tmpgrp]} {
						if {$options(rooms2log) == ""} {
							lappend grpjids $tmpgrp
						} elseif {[string match *$tmpgrp* $options(rooms2log)]} {
							lappend grpjids $tmpgrp
						}
					}
				}

				all {
					if {$options(rooms2log) == ""} {
						lappend grpjids $tmpgrp
					} elseif {[string match *$tmpgrp* $options(rooms2log)]} {
						lappend grpjids $tmpgrp
					}
				}
			}
		}
	}
	return $grpjids
}

proc bldjid::valid_groups_log {grplist pattern} {
# Filter out wrong conferences when searching in log file.
# We always search with loose rules and "all" parameter
# (not only in rooms where we have admin's rights).
	if {$pattern == ""} {
		return $grplist
	} else {
		set grpjids ""
		foreach tmpgrp $grplist {
			if {[string match *$pattern* $tmpgrp]} {
				lappend grpjids $tmpgrp
			}
		}
	}
	return $grpjids
}

proc bldjid::pickup {index chatid} {
	variable user_list
	set group [::chat::get_jid $chatid]
	set maxindex [expr {[llength $user_list] - 1}]
	if {$maxindex < 0} {
# Nothing to show: list is empty. There's a check for it up there, however.
		chat::add_message $chatid $group error [::msgcat::mc "The list is empty."] {}
		return stop
	} elseif {$index > $maxindex || $index < 0} {
		chat::add_message $chatid $group error [::msgcat::mc "Index out of range."] {}
		return stop
	} else {
		set jid [lindex [lindex $user_list $index] 2]
		return $jid
	}
}

proc bldjid::visitors {xlib chatid room group} {
	variable options
	variable user_list
	if {$options(enable_jids_gathering)} {
		global jids_by_chats
		set user_list {}
		set header [::msgcat::mc "Group filtering is "]
		if {$options(filter_admin_rooms)} {
			set which admin
			append header [::msgcat::mc "ON."]
		} else {
			set which all
			append header [::msgcat::mc "OFF."]
		}
		append header [::msgcat::mc " Show all visits to the "]
		if {$room == ""} {
			set room $group
			append header [::msgcat::mc "current room:"]
		} elseif {$options(matching_rules) == "exact"} {
			append header [::msgcat::mc "room %s:" $room]
		} else {
			append header [::msgcat::mc "rooms that match *%s*:" $room]
		}

		if {$options(matching_rules) == "exact" \
		&& ![info exists jids_by_chats($room)]} {
			chat::add_message $chatid $group error [::msgcat::mc "List for the requested room doesn't exist.\
			Make sure you provide a full room's JID, if JIDs gathering is turned on,\
			or if you use Loose match rules."] {}
			return stop
		} elseif {$options(matching_rules) == "exact"} {
			if {$options(filter_admin_rooms)} {
				set iam [whoami $xlib $room]
				if {![string match *moderator* $iam]} {
					chat::add_message $chatid $group error $filter_turned_on {}
					return stop
				}
			}
			if {[info exists jids_by_chats($room)]} {
				set user_list $jids_by_chats($room)
			}
		} else {
			set groupjids [[namespace current]::valid_groups $xlib $which $room]
			foreach grp $groupjids {
				if {[info exists jids_by_chats($grp)]} {
					foreach item $jids_by_chats($grp) {
### TODO: It's a bug. If locale is other than English one,
### $header never matches "*current room:"
### so the room's name will always be printed.
### So workaround added until I figure out how to fix it.
						if {![string match "*current room:" $header] \
						 && ![string match "*текущую комнату:" $header]} {
							lappend item $grp
						}
						lappend user_list $item
					}
				}
			}
		}
		set msg [[namespace current]::prepare_print_res 0]
		[namespace current]::print_search_res $chatid $group $header $msg
	} else {
		chat::add_message $chatid $group error [::msgcat::mc "JIDs gathering is disabled."] {}
	}
}

proc bldjid::visited {xlib chatid room group nick} {
	variable options
	variable user_list
	if {$options(enable_jids_gathering)} {
		global jids_by_chats
		set user_list {}
		set header [::msgcat::mc "Group filtering is "]
		if {$options(filter_admin_rooms)} {
			set which admin
			append header [::msgcat::mc "ON."]
		} else {
			set which all
			append header [::msgcat::mc "OFF."]
		}
		append header [::msgcat::mc " Show visits of "]
		if {$nick == ""} {
			chat::add_message $chatid $group error \
			[::msgcat::mc "Parameters expected. Type /bldhelp for usage examples."] {}
			return stop
		} elseif {$room == ""} {
			set groupjids ""
			if {$options(matching_rules) == "exact"} {
				append header [::msgcat::mc "%s to all rooms:" $nick]
				set groupjids [[namespace current]::valid_groups $xlib $which {}]
			} else {
				append header [::msgcat::mc "users matching *%s* to all rooms:" $nick]
			}
			if {[lindex $nick 0] == "log"} {
				[namespace current]::read_log
				variable jbc_log
				set groupjids [[namespace current]::valid_groups_log \
				[array names jbc_log] $room]
				set if_log 1
			} else {
				set groupjids [[namespace current]::valid_groups $xlib $which {}]
				set if_log 0
			}
			[namespace current]::prepare_user_list $groupjids $nick
		} else {
			if {$options(matching_rules) == "exact"} {
				append header [::msgcat::mc "%s to the room %s:" $nick $room]
			} else {
				append header [::msgcat::mc "users matching *%s*\
				to the rooms that match *%s*:" $nick $room]
			}
			if {$options(filter_admin_rooms)} {
				set iam [whoami $xlib $group]
				if {![string match *moderator* $iam]} {
					chat::add_message $chatid $group error \
					[::msgcat::mc "You turned off showing results in the rooms\
					where you don't have admin privileges."] {}
					return stop
				}
			}
			if {[lindex $nick 0] == "log"} {
				[namespace current]::read_log
				variable jbc_log
				set groupjids [[namespace current]::valid_groups_log \
				[array names jbc_log] $room]
				set if_log 1
			} else {
				set groupjids [[namespace current]::valid_groups $xlib $which $room]
				set if_log 0
			}
			[namespace current]::prepare_user_list $groupjids $nick
		}
		set msg [[namespace current]::prepare_print_res $if_log]
		[namespace current]::print_search_res $chatid $group $header $msg
	} else {
		chat::add_message $chatid $group error [::msgcat::mc "JIDs gathering is disabled."] {}
	}
	if {[info exists jbc_log]} {
# If we've searched in log file, we'd better discard the array now to free memory.
# Although cacheing for some time could be added (for 5 minutes or so).
		catch {unset jbc_log}
	}
}

proc bldjid::prepare_print_res {if_log} {
	variable user_list
	set msg ""
	set n 0
	foreach user $user_list {
		lappend msg [[namespace current]::format_msg $user $n $if_log]
		incr n
	}
	return $msg
}

proc bldjid::print_search_res {chatid group header msg} {
	variable options
	if {$options(redirect_output)} {
		[namespace current]::toggle_monitor $chatid 0
		[namespace current]::add_message_to_monitor $chatid $header
		[namespace current]::add_message_to_monitor $chatid "[join $msg]\n\n"
	} else {
		chat::add_message $chatid $group error $header {}
		chat::add_message $chatid $group info "[join $msg]" {}
	}
	return
}

proc bldjid::help {chatid group} {
	set header [::msgcat::mc "Short usage examples for Bldjid plugin"]
	lappend msg [::msgcat::mc "\nBan in all rooms where you have admin privileges:\
	\n/bldjid JID\
	\nSome reason for banning (optional).\
	\n\nBan a list of JIDs in the current room:\
	\n/massban JID1 JID2 JID3 ...\
	\nSome reason for banning (optional).\
	\n\nUnban a banned person in these rooms:\
	\n/unbldjid JID\
	\n\nUnban all people banned in the current room:\
	\n/amnesty\
	\n\nUnban people in the current room whose ban reason matches *some reason*:\
	\n/amnesty some reason\
	\n\nSearch for a pattern in the outcast list of the current room:\
	\n/inbanlist pattern\
	\n\nSearch for entries that don\'t match a pattern, in the member list of the current room\
	\n(note ! separated with a space to invert results):\
	\n/inmemberlist ! pattern\
	\n\nShow all visitors of the current room:\
	\n/visitors\
	\n\nShow all visitors of rooms that match *room*:\
	\n/visitors room\
	\n\nShow visits made by someone whose nick matches *nick*:\
	\n/visited nick\
	\n\nShows visits made by *nick*, to the rooms that match *room*:\
	\n/visited nick\
	\nroom\
	\n\nThe same fashion as previous, but looking for\
	\njids, roles, affiliations and versions:\
	\n/visited jid john\
	\n/visited role moder\
	\n/visited aff none\
	\n/visited ver Tkabber\
	\n/visited ver Debian\
	\n\nAn enhancement of the previous command that allows searching in log file:\
	\n/visited log nick\
	\n/visited log jid mylogin@jabberserver.org\
	\nroom\
	\n/visited log ver Gajim Version: 0.12.1 OS: Windows XP\
	\nAnd even combining different search parameters! (ver should be the last one):\
	\n/visited log nick bigote aff owner ver Tkabber Version: 0.11.1-svn-20100223\
	\nfoo@conf\
	\n\nBan in all rooms where you have admin privileges:\
	\n?n? means an item number from a list given by a /visit* command.\
	\n/bldjid ?n?\
	\nSome reason for banning (optional).\
	\n\nClear the current monitor window:\
	\n/clearmon\
	\n\nSet a list of items to watch their entrance in your rooms.\
	\nValid keywords are: nick, jid, hash, ver. You can provide several hashes\
	but the rest of parameters only can be given one at a time.\
	\nIf no action is specified, a notification window will be displayed.\
	\n/watch keyword parameter\
	\n\nSet an action (say, devoice, kick or ban) for watched item.\
	\nIf no reason is specified, default ones will be used.\
	\nIn case of devoice the reason is sent to the visitor as a message.\
	\n/watch nick bigote\
	\nsay Thank you for your cool plugin, dude! :)\
	\n/watch ver Talkonaut\
	\nkick\
	\n\nThe same with a reason:\
	\n/watch ver Talkonaut\
	\nkick Please change your client and then come back!\
	\n\nSet an action with some exception that can be either !jid or !aff:\
	\n/watch hash a9u6eq0vXVwocThpLYzZreN333D= fNqQsAZ2tdL90QDpShd4yj4uSNU=\
	\ndevoice !aff member\
	\n\nThe same with a reason (please note a keyword \'reason\' must be used for separation):\
	\n/watch jid (k|c)\[оОoO0óöÓÖ\]{2}l@jabber..*\
	\nban !jid c\[oO\]{2}l@jabber.(ru|org) reason You are not welcome here!\
	\nNote that jids for both parameter and exception should be given as a regexp.\
	\n\nDelete an item (or all of them) from your watchlist:\
	\n/unwatch keyword parameter\
	\n/unwatch keyword all\
	\n/unwatch all\
	\n\nShow your watchlist:\
	\n/listwatch\
	\n\nSet or unset captcha protection or members only state for your room:\
	\n/captcha ?n?\
	\n/monly ?n?\
	\nwhere ?n? can be \'1\', \'on\' or \'ON\' to set and \'0\', \'off\' or \'OFF\' to unset.\
	\nUsing it without parameters returns a current state of a requested setting in the room.\
	\n\nTo toggle between Show and Close monitor states use <Control-m> shortcut.\
	\n\nSee README file for more information.\
	\nVisit Tkabber -> Customize -> Plugins -> Bldjid\
	\nto tune the plugin for your needs."]
	[namespace current]::print_search_res $chatid $group $header $msg
}

### Thanks to Alexey Smirnov <alexey.smirnov@gmx.com>
proc bldjid::extract_user {msg tsVar nickVar jidVar r_affVar v_osVar v_hashVar} {
	upvar 1 $tsVar timestamp $nickVar nick $jidVar jid \
	$r_affVar r_aff $v_osVar v_os $v_hashVar v_h

	set timestamp [lindex $msg 0]
	set nick      [lindex $msg 1]
	set jid       [lindex $msg 2]
	set r_aff     [lindex $msg 3]
	set v_os      [lindex $msg 4]
	set v_h       [lindex $msg 5]

	return
}

### Thanks to Alexey Smirnov
proc bldjid::format_msg {msg n if_log} {
	variable options
	if {$if_log} {
		set l 6
	} else {
		set l 7
	}
	set s $options(custom_separator)
	[namespace current]::extract_user $msg timestamp nick jid r_aff v_os v_h
	if {$jid == ""} {
		set jid "*NO JID*"
	}
	if {[llength $msg] == $l} {
		set myroom ""
	} else {
		set myroom " $s [lindex $msg $l]"
	}
	if {$options(log_role_aff)} {
		set myroleaff " $s $r_aff"
	} else {
		set myroleaff ""
	}
	if {$v_h != ""} {
		set myvhash " $s $v_h"
	} else {
		set myvhash ""
	}
	if {$options(log_ver_os)} {
		set myvos " $s $v_os"
	} else {
		set myvos ""
	}
		return "\n$n:\t$timestamp $s $nick $s $jid$myroleaff$myvos$myvhash$myroom"
}

proc bldjid::prepare_user_list {grpjds par} {
	variable options
	variable user_list
	global jids_by_chats
	variable jbc_log
	set if_log 0
	if {[lindex $par 0] == "log"} {
		set par [join [lreplace $par 0 0]]
		set if_log 1
	}
# Parameter parsing
	set pll [llength $par]
	for {set x 0} {$x < $pll} {incr x 2} {
		set key [lindex $par $x]
		switch -- $key {
			nick {
				set p_list(1) [lindex $par $x+1]
			}
			jid {
				set p_list(2) [lindex $par $x+1]
			}
			role - aff {
# TODO: Add in search routine below a possibility to search for role and aff separately.
# It's being searched matching to complete word like "participant/none" now,
# without distinguishing role and affiliation.
				set p_list(3) [lindex $par $x+1]
			}
			ver {
				set p_list(4) [regsub -all {([^a-zA-Z0-9_])} \
				[join [lrange $par $x+1 end]] {\\\0}]
				break
			}
			hash {
				set p_list(5) [lindex $par $x+1]
			}
			default {
				set p_list(1) $par
				break
			}
		}
	}
	if {$if_log && [info exists jbc_log]} {
		array set a [array get jbc_log]
	} elseif {[info exists jids_by_chats]} {
		array set a [array get jids_by_chats]
	} else {
		return stop
	}
	foreach grp $grpjds {
		foreach item $a($grp) {
			foreach key [array names p_list] {
				if {![info exists s_res]} {
					if {$options(matching_rules) == "exact" && $key != 3} {
						set s_res [string equal $p_list($key) \
						[lindex $item $key]]
					} elseif {$key == 5} {
						set s_res [string match \
						*[string map {\[ \\\[ \] \\\]} $p_list($key)]* \
						[lindex $item $key]]
					} else {
						set s_res [string match -nocase \
						*[string map {\[ \\\[ \] \\\]} $p_list($key)]* \
						[lindex $item $key]]
					}
				} else {
					if {$options(matching_rules) == "exact" && $key != 3} {
						set s_res [expr $s_res & \
						[string equal $p_list($key) \
						[lindex $item $key]]]
					} elseif {$key == 5} {
						set s_res [expr $s_res & \
						[string match \
						*[string map {\[ \\\[ \] \\\]} $p_list($key)]* \
						[lindex $item $key]]]
					} else {
						set s_res [expr $s_res & [string match -nocase \
						*[string map {\[ \\\[ \] \\\]} $p_list($key)]* \
						[lindex $item $key]]]
					}
				}
			}
			if {$s_res} {
					lappend item $grp
					lappend user_list $item
			}
			catch {unset s_res}
		}
	}
	catch {unset a}
}

proc bldjid::prepare_watch {par group_cmd} {
	variable options
	variable user_list
	variable watch_id
	global jids_by_chats

	set par [join $par]
	if {$par == ""} {
		return
	}
# Parameter parsing
	set pll [llength $par]
	for {set x 0} {$x < $pll} {incr x 2} {
		set key [lindex $par $x]
		switch -- $key {
# Since nick can consist of several words it must be the last element of parameters.
			nick {
				set w_list(watch_id,p,1) [lrange $par $x+1 end]
				break
			}
# We allow users to specify several jids in one watch, so this element must be at the and, too.
			jid {
				set w_list(watch_id,p,2) [lrange $par $x+1 end]
				break
			}
			role - aff {
				set w_list(watch_id,p,3) [lindex $par $x+1]
			}
# The same as above, ver must be the last element.
			ver {
				set w_list(watch_id,p,4) [regsub -all {([^a-zA-Z0-9_])} \
				[join [lrange $par $x+1 end]] {\\\0}]
				break
			}
			hash {
				set w_list(watch_id,p,5) [lindex $par $x+1]
			}
			default {
# Nick is default.
				set w_list(watch_id,p,1) $par
				break
			}
		}
	}

# Group/action parsing
	if {$group_cmd != ""} {
		set gll [llength $group_cmd]
		for {set x 0} {$x < $gll} {incr x} {
			if {[string match *@* [lindex $group_cmd $x]]} {
				set w_list(watch_id,g,$x) [lindex $group_cmd $x]
			} else {
				set c [lindex $group_cmd $x]
				switch -- $c {
					alert - kick - voice - devoice - moder - member - ban {
						lappend w_list(watch_id,cmd) $c
					}
					default {
						return -1
					}
				}
			}
		}
	} else {
		return -2
	}
	incr watch_id
}

# Thanks to Konstantin Khomoutov (kostix@007spb.ru)
proc bldjid::is_any_integer {a} {
	regexp \\d+ [string trim $a]
}

# These two next procs are taken from snoopstatus.tcl and modified a bit.
proc bldjid::create_monitor_window {chatid} {
	set ccw [::chat::winid $chatid]
	set wmon $ccw.mon
	frame $wmon
	pack $wmon -side top -fill x
	text $wmon.text -state disabled -wrap word \
		-yscrollcommand [list $ccw.vsb set]
	scrollbar $ccw.vsb -command [list $wmon.text yview]

	grid $wmon.text $ccw.vsb -sticky ns
	grid $wmon.text -sticky news
	grid rowconfigure $wmon 0 -weight 1
	grid columnconfigure $wmon 0 -weight 1

	regsub -all %W [bind Text <Prior>] $wmon.text prior_binding
	regsub -all %W [bind Text <Next>] $wmon.text next_binding
	bind $ccw.input <Shift-Meta-Prior> $prior_binding
	bind $ccw.input <Shift-Meta-Next> $next_binding
	bind $ccw.input <Shift-Alt-Prior> $prior_binding
	bind $ccw.input <Shift-Alt-Next> $next_binding
	bind $ccw.input <Shift-Meta-Prior> +break
	bind $ccw.input <Shift-Meta-Next> +break
	bind $ccw.input <Shift-Alt-Prior> +break
	bind $ccw.input <Shift-Alt-Next> +break
}

proc bldjid::add_message_to_monitor {chatid body} {
	set ccw [::chat::winid $chatid]
	set t $ccw.mon.text
	if {![winfo exists $t]} return

	append msg $body

	$t configure -state normal
	$t insert end $msg
	$t configure -state disabled
	$t see end
}

proc bldjid::clear_monitor {chatid} {
	set ccw [::chat::winid $chatid]
	set t $ccw.mon.text
	if {![winfo exists $t]} return
	$t configure -state normal
	$t delete 0.0 end
	$t configure -state disabled
}

# I have doubts if it's OK to use catch results as a condition...
proc bldjid::toggle_monitor {chatid toggle} {
	variable options
	if {!$options(redirect_output)} { return }

	set ccw [::chat::winid $chatid]
	if {![catch {pack info $ccw.mon} res] && $toggle} {
		pack forget $ccw.mon $ccw.vsb
	} elseif {[string match "*bad window path name*" $res]} {
		[namespace current]::create_monitor_window $chatid
	} elseif {[string match "*isn\'t packed*" $res]} {
		pack $ccw.mon -side bottom -fill both
	}
}

proc bldjid::process_watch {pattern room nick jid role_aff chatid action iam} {
	set xlib [::chat::get_xlib $chatid]
	set reason ""
	set except ""
	if {[llength $action] > 1} {
		set act [lindex $action 0]
		if {[string index [lindex $action 1] 0] == "!"} {
			set line [lrange $action 1 end]
			set reas_i [lsearch -exact $line reason]
			if {$reas_i > 1 && $reas_i < [expr ([llength $line] - 1)]} {
				set except [lrange $line 1 [expr ($reas_i - 1)]]
				set reason [lrange $line [expr ($reas_i + 1)] end]
			} else {
				set except [lrange $line 1 end]
			}
			set ex [lindex $action 1]
			switch -- $ex {
				!jid {
					if {[regexp -nocase $except $jid]} {
						return
					}
				}
				!aff {
					foreach ex_a $except {
						if {[string match *$ex_a* $role_aff]} {
							return
						}
					}
				}
				default {
					chat::add_message $chatid $room error \
					[::msgcat::mc "Wrong type of exception \'%s\' was specified.\
					It should be one of the following: !jid or !aff.\
					No exception has been applied to your action." $ex] {}
				}
			}
		} else {
			set reason [lrange $action 1 end]
		}
	} else {
		set act $action
	}
	switch -- $act {
		devoice {
			if {[string match *moderator* $iam]} {
				if {$reason == ""} {
					set reason [::msgcat::mc "You have been devoiced automatically.\
					Most likely, there was a good reason to do so.\
					However, it might happen by mistake. If you think it is the case\
					please wait for me or ask someone of moderators to give you voice.\
					Sorry for inconvenience."]
				}
				message::send_msg $xlib $room/$nick -type chat -body $reason
				muc::change_item_attr $xlib $room/$nick role visitor down {} $chatid
				set add [::msgcat::mc " and has been devoiced"]
			} else {
				set add [::msgcat::mc " but you don't have rights to %s him/her" $act]
			}
		}
		kick {
			if {[string match *moderator* $iam]} {
				if {$reason == ""} {
					set reason [::msgcat::mc "Autokicked by bldjid"]
				}
				muc::change_item_attr $xlib $room/$nick role none down $reason $chatid
				set add [::msgcat::mc " and has been kicked"]
			} else {
				set add [::msgcat::mc " but you don't have rights to %s him/her" $act]
			}
		}
		ban {
			if {[string match *admin* $iam]} {
				if {$reason == ""} {
					set reason [::msgcat::mc "Autobanned by bldjid"]
				}
				muc::change_item_attr $xlib $room/$nick affiliation outcast down $reason $chatid
				set add [::msgcat::mc " and has been banned"]
			} else {
				set add [::msgcat::mc " but you don't have rights to %s him/her" $act]
			}
		}
		say {
			if {$reason == ""} {
				set reason [::msgcat::mc "It's an automated message sent by Bldjid plugin.\
				The boss wanted to say you something but it seems he/she forgot\
				to leave a message :)"]
			}
			message::send_msg $xlib $room/$nick -type chat -body $reason
			set add [::msgcat::mc " and your message \'%s\' was sent to him/her" $reason]
		}
		default {
			if {$jid == ""} {
				set jid [::msgcat::mc "not available"]
			}
			notify_jid $pattern $room $nick $jid $chatid
			set add [::msgcat::mc " and, since no action was specified,\
			a notification window has been displayed"]
		}
	}
	if {$jid == ""} {
		set jid [::msgcat::mc "not available"]
	}
	set ts [clock format [clock seconds] -format {%Y-%m-%d_%H:%M:%S}]
	set msg [::msgcat::mc "%s   Watched %s has entered this room as %s (jid: %s)%s.\n" $ts $pattern $nick $jid $add]
	[namespace current]::toggle_monitor $chatid 0
	[namespace current]::add_message_to_monitor $chatid $msg
	[namespace current]::log_msg $msg
#	[namespace current]::add_message_to_monitor $chatid "[join $msg]\n\n"
}

proc bldjid::notify_jid {pattern room nick jid chatid} {
	variable winid
	set w .notify_jid$winid
	incr winid
	if {[winfo exists $w]} {
		destroy $w
	}
	set xlib [::chat::get_xlib $chatid]
	set msg [::msgcat::mc "Watched %s\nhas entered the room %s\nas %s (jid: %s)" \
	$pattern $room $nick $jid]
    Dialog $w -title [::msgcat::mc "Entrance Notify"] \
        -modal none -separator 1 -anchor e -default 0 -parent .
#        set f [$w getframe]
#	frame $w.bf
#	set gap 10
#		grid $w.bf.close -row 0 -column 7 -rowspan 4 -columnspan 2 \
#			-sticky nsew -pady $gap
        $w add -text [::msgcat::mc "Close"] -command "destroy $w"
        $w add -text [::msgcat::mc "Open chat"] -command "destroy $w; \
		[list chat::open_to_user $xlib $room/$nick]"
#        $w add -text [::msgcat::mc "Grant Voice"] -command "destroy $w; \
#		[list muc::change_item_attr $xlib $room/$nick role participant up {} $chatid]"
        $w add -text [::msgcat::mc "Revoke Voice"] -command "destroy $w; \
		[list muc::change_item_attr $xlib $room/$nick role visitor down {} $chatid]"
#        $w add -text [::msgcat::mc "Grant Membership"] -command "destroy $w; \
#		[list muc::change_item_attr $xlib $room/$nick affiliation member up {} $chatid]"
#        $w add -text [::msgcat::mc "Grant Moderator Privileges"] -command "destroy $w; \
#		[list muc::change_item_attr $xlib $room/$nick role moderator up {} $chatid]"
        $w add -text [::msgcat::mc "Kick"] -command "destroy $w; \
		[list muc::change_item_attr $xlib $room/$nick role none down {} $chatid]"
        $w add -text [::msgcat::mc "Ban"] -command "destroy $w; \
		[list muc::change_item_attr $xlib $room/$nick affiliation outcast down {} $chatid]"
        label $w.lmsg -padx 10 -pady 5 -text $msg
        pack $w.lmsg -expand yes -fill both
        $w draw
}

# Taken from Antispam plugin
proc bldjid::log_msg {msg} {
    variable options
    set log $options(log_file)
    if { $log eq "" } {
        return
    }
    if { [catch {open $log a} fd] } {
        debugmsg bldjid \
        "ERROR: cannot open log file."
        return
    }
    puts $fd "$msg"
    close $fd
}

proc bldjid::read_log {} {
    variable options
    variable jbc_log
    set log $options(log_file)
    if { $log eq "" } {
        return
    }
    if { [catch {open $log} fd] } {
        debugmsg bldjid \
        "ERROR: cannot open log file."
        return
    }
	set data [read $fd]
    close $fd
	set lines [split $data \n]
# Log format
#	set msg4log "$ent_cntr\t$ts\t$group\t$nick\t$rjid\t$role_aff\t$ver_os\t$ver_hash"
# jids_by_chats format
#	lappend jids_by_chats($group) \
#	[list $ts $nick $rjid $role_aff $ver_os $ver_hash]
	foreach line $lines {
		set e [split $line \t]
		set ts [lindex $e 1]
		set grp [lindex $e 2]
		set nick [lindex $e 3]
		set rjid [lindex $e 4]
		if {[llength $e] >= 6} {
			set r_aff [lindex $e 5]
		} else {
			set r_aff ""
		}
		if {[llength $e] >= 7} {
			set v_h [lindex $e 6]
		} else {
			set v_h ""
		}
		if {[llength $e] == 8} {
			set v_os [lindex $e 7]
		} else {
			set v_os ""
		}
		lappend jbc_log($grp) [list $ts $nick $rjid $r_aff $v_h $v_os]
	}
}

proc bldjid::request_iq {xlib chatid group nick ts real_jid role_aff ver_hash node pid my_id} {
    ::xmpp::sendIQ $xlib get \
	-query [::xmpp::xml::create query \
	-xmlns jabber:iq:version] \
	-to $group/$nick \
	-command [list [namespace current]::parse_info_iqversion \
		$chatid $group $nick $ts $real_jid $role_aff $ver_hash $node $pid $my_id]
}

proc bldjid::parse_info_iqversion {chatid group nick ts rjid role_aff ver_hash node pid my_id res child} {
	variable ent_cntr
	variable options
	variable watched_jids
	global jids_by_chats
    if {![winfo exists [chat::chat_win $chatid]]} {
		return
    }
	set ver_os ""
	set jid $group/$nick
    if {![cequal $res ok]} {
		set ver_os "*NO VER/OS*"
    }

    ::xmpp::xml::split $child tag xmlns attrs cdata subels

	userinfo::parse_iqversion_item $jid $subels
    foreach {i j} [list clientname    [::msgcat::mc "Client:"] \
		    clientversion [::msgcat::mc "Version:"] \
		    os            [::msgcat::mc "OS:"]] {
		if {[info exists userinfo::userinfo($i,$jid)] && \
		 ![cequal $userinfo::userinfo($i,$jid) ""]} {
	    	append ver_os " $j $userinfo::userinfo($i,$jid)"
		}
    }
	set ver_os [string trim $ver_os]
	set ver_os [join [split $ver_os \n] ""]
	if {[string equal \*NO\ VER\/OS\* $ver_os] && $node != ""} {
		set ver_os $node
	}
	set i 0
	foreach val $jids_by_chats($group) {
		if {[string equal $my_id [lindex [split $val \ ] end]] \
		 && [string match *$nick* $val] \
		 && [string match *Timeout* $val]} {
		 	set val [lreplace $val 4 4 $ver_os]
			set jids_by_chats($group) [lreplace $jids_by_chats($group) $i $i $val]
			if {$options(log_file) != ""} {
				set msg4log \
				"$ent_cntr\t$ts\t$group\t$nick\t$rjid\t$role_aff\t$ver_os\t$ver_hash"
				[namespace current]::log_msg $msg4log
				incr ent_cntr
			}
			after cancel $pid
			break
		}
		incr i
	}
# Checking for watched "normal" version
	if {[info exists watched_jids(ver)] \
	&& ($ver_os != "Timeout" || $ver_os != "*NO VER/OS*")} {
		set pattern [string map {\{ \\\{ \} \\\}} $ver_os]
		set xlib [::chat::get_xlib $chatid]
		set iam [whoami $xlib $group]
		if {[string match -nocase *$watched_jids(ver)* $pattern] \
		&& ![string match *%*@irc* $group] \
		&& ([string match *$group* $options(watch_groups)] \
		||  $options(watch_groups) == "")} {
			set bare_jid [::xmpp::jid::stripResource $rjid]
			[namespace current]::process_watch \
			$pattern $group $nick $bare_jid $role_aff $chatid \
			$watched_jids(action) $iam
		}
	}
}

proc bldjid::process_ver {chatid nick ts group rjid role_aff ver_hash node iam my_id} {
	variable ent_cntr
	variable options
	variable watched_jids
	global jids_by_chats
	if {$options(log_file) == ""} {
		return
	}
	set i 0
	foreach val $jids_by_chats($group) {
		if {[string equal $my_id [lindex [split $val \ ] end]] \
		 && [string match *$nick* $val] \
		 && [string match *Timeout* $val]} {
		 	set ver_os [lindex $val 4]
# If we are watching for someone with "Timeout" or "*NO VER/OS*" version...
			if {[info exists watched_jids(ver)] \
			&&  ($ver_os == "Timeout" || $ver_os == "*NO VER/OS*")} {
				set pattern [string map {\{ \\\{ \} \\\}} $ver_os]
				if {[string match -nocase *$watched_jids(ver)* $pattern] \
				&& ![string match *%*@irc* $group] \
				&& ([string match *$group* $options(watch_groups)] \
				||  $options(watch_groups) == "")} {
					set bare_jid [::xmpp::jid::stripResource $rjid]
					[namespace current]::process_watch \
					$pattern $group $nick $bare_jid $role_aff $chatid \
					$watched_jids(action) $iam
				}
			}
# Saving to log and to array a node as a version, if possible.
		 	if {$node != ""} {
		 		set val [lreplace $val 4 4 "Timeout $node"]
				set msg4log \
				"$ent_cntr\t$ts\t$group\t$nick\t$rjid\t$role_aff\tTimeout $node\t$ver_hash"
			} else {
				set msg4log \
				"$ent_cntr\t$ts\t$group\t$nick\t$rjid\t$role_aff\tTimeout\t$ver_hash"
			}
			[namespace current]::log_msg $msg4log
			incr ent_cntr
			break
		}
		incr i
	}
}

# Taken from Clienticons plugin, thanks to Jet <megaxbit@xmpp.ru>!
proc bldjid::caps_collector {xlib jid type x args} {
	variable options
	if {!$options(log_ver_hash)} {
		return
	}
	variable jidver
	variable jidnodes
	set group [::xmpp::jid::stripResource $jid]
	set chatid [chat::chatid $xlib $group]
	set nick [::xmpp::jid::resource $jid]
	if {[chat::is_groupchat $chatid]} {
		foreach xdata $x {
			if {[::xmpp::xml::getAttr $xdata c] == "http://jabber.org/protocol/caps"} {
				::xmpp::xml::split $xdata tag xmlns attrs cdata subels
				set jidnodes($nick,$chatid) [::xmpp::xml::getAttr $attrs node]
				set jidver($nick,$chatid) [::xmpp::xml::getAttr $attrs ver]
				return
			}
		}
	}
}

# Taken from muc.tcl
proc bldjid::request_config {chatid state cmd} {
    ::xmpp::sendIQ [chat::get_xlib $chatid] get \
		-query [::xmpp::xml::create query \
						-xmlns $::NS(muc#owner)] \
		-to [chat::get_jid $chatid] \
		-command [list [namespace current]::receive_config $chatid $state $cmd]
}

proc bldjid::receive_config {chatid state cmd res child} {
    set group [chat::get_jid $chatid]
    if {![string equal $res ok]} {
		chat::add_message $chatid $group error \
			[::msgcat::mc "Error getting room config: %s" [error_to_string $child]] \
			{}
		return
	}

	::xmpp::xml::split $child tag xmlns attrs cdata subels

	lassign [::xmpp::data::findForm $subels] type form
	if {[string equal $type form]} {
		set fields [::xmpp::data::parseForm $form]
	}
	set if_cmd 0
	foreach {tag item} $fields {
		if {$tag == "field"} {
			lassign $item var type label desc required options vals media_list
			if {$var == $cmd} {
				switch -- $cmd {
					captcha_protected {
						set phrase [::msgcat::mc "protected by captcha"]
						set if_cmd 1
					}
					muc#roomconfig_membersonly {
						set phrase [::msgcat::mc "members only"]
						set if_cmd 1
					}
				}
				set not {}
				set msg_type "info"
				if {!$vals} {
					set not [::msgcat::mc " NOT"]
					set msg_type "error"
				}
			}
		}
	}

	if {($state == "1" || $state == "on" || $state == "ON") && $not != ""} {
		chat::add_message $chatid $group error \
		[::msgcat::mc "WILL BE IMPLEMENTED TOMORROW ;-)"] {}
	} elseif {($state == "0" || $state == "off" || $state == "OFF") && $not == ""} {
		chat::add_message $chatid $group error \
		[::msgcat::mc "WILL BE IMPLEMENTED TOMORROW ;-)"] {}
	} else {
		if {$if_cmd} {
			chat::add_message $chatid $group $msg_type \
			[::msgcat::mc "Your room is%s %s" $not $phrase] {}
		} else {
			chat::add_message $chatid $group error \
			[::msgcat::mc "It seems the server doesn't support this feature."] {}
		}
	}
	return
}

proc bldjid::send_config {chatid restags} {
	set xlib [chat::get_xlib $chatid]
	set group [chat::get_jid $chatid]
	::xmpp::sendIQ $xlib set \
		-query [::xmpp::xml::create query \
					-xmlns $::NS(muc#owner) \
					-subelements $restags] \
		-to $group \
		-command [list muc::test_error_res \
					[::msgcat::mc "Sending configure form"] $xlib $group $chatid]
}

proc bldjid::amoder {xlib group nick chatid} {
	set iam [whoami $xlib $group]
	if {[string match *admin* $iam] || [string match *owner* $iam]} {
		muc::change_item_attr $xlib $group/$nick role moderator up {} $chatid
	}
}
