# "Bldjid2" plugin for Tkabber. 2011-02-09 v. 0.1.0.1
# Written by Serge Yudin xmpp:bigote@jabber.ru
# See README or README.ru file for usage.

namespace eval bldjid2 {
    ::msgcat::mcload [file join [file dirname [info script]] msgs]
    if {![::plugins::is_registered bldjid2]} {
	::plugins::register bldjid2 \
		-namespace [namespace current] \
		-source [info script] \
		-description [::msgcat::mc "Whether the Bldjid2 plugin is loaded."] \
		-loadcommand [namespace code load] \
		-unloadcommand [namespace code unload]
	return
    }
    package require sqlite3
	set winid 0

# Set plugin options.
	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 Bldjid2 [::msgcat::mc "Bldjid2 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 Bldjid2

	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 Bldjid2

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

	custom::defvar options(base_file) [file join $::configdir bldjid2.sqlite] \
	    [::msgcat::mc "Bldjid2 database file. If empty, a default one\
	    will be created. You need to restart Tkabber after applying a new name."] \
	    -group Bldjid2 \
	    -type file

	custom::defvar options(base_backup) 0 \
	    [::msgcat::mc "Backup your base each time Tkabber starts.\
	    Caution with big files! You may run out of disk space!"] \
	    -type boolean -group Bldjid2

	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 Bldjid2

	custom::defvar options(reconnect) 5 \
	    [::msgcat::mc "Visitor's reconnection timeout after which\
	    a new version and time requests are sent. If a visitor enters\
	    a room within this interval, old values cached in the base\
	    are taken (i.e. we hope it's the same client reconnected)."] \
	    -type integer -group Bldjid2

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

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

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

	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 Bldjid2

	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 Bldjid2

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

	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 Bldjid2

	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 Bldjid2

    if {$options(base_file) == ""} {
	set $options(base_file) [file join $::configdir bldjid2.sqlite]
    }
    if {[file exists $options(base_file)]} {
#	file copy $options(base_file) $options(base_file).[clock seconds]
	sqlite3 db $options(base_file)
    } else {
	sqlite3 db $options(base_file)
	db eval {
	    CREATE TABLE client(
		id INTEGER PRIMARY KEY,
		cli TEXT
	    );
	}
	db eval {
	    CREATE TABLE version(
		id INTEGER PRIMARY KEY,
		ver TEXT
	    );
	}
	db eval {
	    CREATE TABLE osys(
		id INTEGER PRIMARY KEY,
		os TEXT
	    );
	}
	db eval {
	    CREATE TABLE timezone(
		id INTEGER PRIMARY KEY,
		tz TEXT
	    );
	}
	db eval {
	    CREATE TABLE visitors(
		id INTEGER PRIMARY KEY,
		ts INTEGER,
		grp TEXT,
		nick TEXT,
		jid TEXT,
		res TEXT,
		role TEXT,
		aff TEXT,
		status_h TEXT,
		caps_h TEXT,
		avatar_h TEXT,
		photo_h TEXT,
		cli_id INTEGER,
		ver_id INTEGER,
		os_id INTEGER,
		tz_id INTEGER,
		bmod_ver TEXT,
		pres_lang TEXT,
		pres_id TEXT,
		pres_h TEXT,
		prty INTEGER,
		unavail INTEGER,
		part_vis_h TEXT,
		full_vis_h TEXT
	    );
	}
	db eval {
	    CREATE TABLE alias(
		id INTEGER,
		alias TEXT,
		userids TEXT,
		comment TEXT
	    );
	}
    }
}

proc bldjid2::load {} {
# A variable where the last query result is stored.
    variable user_list
    variable watched_jids
    variable watch_id 0
    variable presence_data
    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]::presence_data_collector 100
    hook::add chat_user_exit \
    [namespace current]::process_unavailable 100
}

proc bldjid2::unload {} {
    variable user_list
    variable watched_jids
    variable watch_id
    variable presence_data
    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]::presence_data_collector 100
    hook::remove chat_user_exit \
    [namespace current]::process_unavailable 100

    catch {unset user_list}
    catch {unset watched_jids}
    catch {unset watch_id}
    catch {unset presence_data}

    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 bldjid2::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 bldjid2::bridge2_smart_enter_exit_message {chatid nick newnick} {
    [namespace current]::process_unavailable $chatid $nick
    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 bldjid2::smart_enter_exit_message {chatid nick} {
    global presence
    variable watched_jids
    variable presence_data
    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] \
     && [::chat::is_groupchat $chatid]} {
	if {$nick == ""} {
	    return
	}
	set ts [clock seconds]
	set xlib [::chat::get_xlib $chatid]
	set real_jid [::muc::get_real_jid $xlib $group/$nick]
	set bare_jid [::xmpp::jid::stripResource $real_jid]
	set resource [::xmpp::jid::resource $real_jid]
	set role [::muc::get_role $xlib $group/$nick]
	set aff [::muc::get_affiliation $xlib $group/$nick]
	if {[info exists presence_data(jidver,$nick,$chatid)]} {
	    if {$presence_data(jidver,$nick,$chatid) == ""} {
		set caps_h "Avalon?"
	    } else {
		set caps_h $presence_data(jidver,$nick,$chatid)
	    }
	} else {set caps_h ""}
	if {[info exists presence_data(jidnodes,$nick,$chatid)]} {
	    set node $presence_data(jidnodes,$nick,$chatid)
	} else {set node ""}
	if {[info exists presence_data(av_hash,$nick,$chatid)]} {
	    set avatar_h $presence_data(av_hash,$nick,$chatid)
	} else {set avatar_h ""}
	if {[info exists presence_data(photo_hash,$nick,$chatid)]} {
	    set photo_h $presence_data(photo_hash,$nick,$chatid)
	} else {set photo_h ""}
	if {[info exists presence_data(bmod_version,$nick,$chatid)]} {
	    set bmod_ver $presence_data(bmod_version,$nick,$chatid)
	} else {set bmod_ver ""}
	if {[info exists presence_data(presence_lang,$nick,$chatid)]} {
	    set pres_lang $presence_data(presence_lang,$nick,$chatid)
	} else {set pres_lang ""}
	if {[info exists presence_data(presence_id,$nick,$chatid)]} {
	    set pres_id $presence_data(presence_id,$nick,$chatid)
	} else {set pres_id ""}
	if {[info exists presence(status,$xlib,$group/$nick)]} {
	    set status_h [[namespace current]::hash \
	    [[namespace current]::get_string4hash \
	    [list $presence(status,$xlib,$group/$nick)]]]
	}
	if {[info exists presence(priority,$xlib,$group/$nick)]} {
	    set prty $presence(priority,$xlib,$group/$nick)
	}
	if {$options(log_file) != ""} {
# Creating PRESENCE hash (only some of the things that come in presence)
# It is required to avoid unnecessary version and time requests
# after our connection is lost and restored.
	    set pres_h [[namespace current]::hash \
	    [[namespace current]::get_string4hash \
	    [list $avatar_h $photo_h $caps_h $bmod_ver $pres_lang]]]
# Things that will be used to calculate PARTIAL visitor hash.
# excluded: nick, role, aff, priority (subjects of change from room to room),
# and jid, resource (for cases we can't see them).
# So, only things that are quite "constant" during a sessions go here.
# Note that cli, ver, os and tz will be added after IQ processing.
	    lappend part_list $caps_h $avatar_h $photo_h $status_h \
	    $bmod_ver $pres_lang
# Things that will be used to calculate FULL visitor hash.
# Note that cli, ver, os and tz will be added after IQ processing.
	    lappend full_list $nick $bare_jid $resource $role $aff \
	    $caps_h $avatar_h $photo_h $status_h $bmod_ver \
	    $pres_lang $prty
	    set iam [whoami $xlib $group]
# Getting max index of the gal's entrance marked with exit timestamp (unavail)
	    set max_e [db eval {SELECT MAX(id) FROM visitors \
		WHERE nick=$nick AND grp=$group AND pres_h=$pres_h}]
	    set exit_time [db eval {SELECT unavail FROM visitors WHERE id=$max_e}]
# If the gal's exit time is an empty string (first entrance) or is more than 0
# (it means she exited some time ago), we create a new entry in the DB.
	    if {$exit_time > 0 || $exit_time == ""} {
		db eval {
		    INSERT INTO visitors
		    VALUES(NULL, $ts, $group, $nick, $bare_jid, \
		    $resource, $role, $aff, $status_h, $caps_h, $avatar_h, \
		    $photo_h, NULL, NULL, NULL, NULL, $bmod_ver, $pres_lang, \
		    $pres_id, $pres_h, $prty, 0, NULL, NULL)
		}
		set id [db eval {SELECT last_insert_rowid();}]
		set sav_caps_h [db eval {SELECT caps_h FROM visitors WHERE id=$max_e}]
# If the visitor enters rapidly enough and her caps hash is the same as the saved one
# (i.e. we hope it's a reconnect of the same client, not start of a new session),
# then we don't request version again taking the saved values from the DB instead.
#if {$exit_time != ""} {
#set recon_time [expr ($ts - $exit_time)]
#puts "$nick's reconnection time in $group: $recon_time"
#}
		if {$exit_time != "" \
		 && [expr ($ts - $exit_time)] < $options(reconnect) \
		 && [string equal $caps_h $sav_caps_h]} {
		    set row [db eval {SELECT * FROM visitors WHERE id=$max_e}]
		    set cli_id [lindex $row 12]
		    set ver_id [lindex $row 13]
		    set os_id [lindex $row 14]
		    set tz_id [lindex $row 15]
		    set part_vis_h [lindex $row end-1]
		    set full_vis_h [lindex $row end]
		    db eval {
			UPDATE visitors
			SET cli_id=$cli_id, ver_id=$ver_id, os_id=$os_id, tz_id=$tz_id, \
			part_vis_h=$part_vis_h, full_vis_h=$full_vis_h
			WHERE id=$id
		    }
# In the contrary case, we do request iq_version (and iq_time after that).
		} else {
		    [namespace current]::request_iq_ver \
		    $xlib $chatid $group $nick $node $part_list $full_list $id
		}
	    }
	}
    }
}

proc bldjid2::process_unavailable {chatid nick} {
    set group [::chat::get_jid $chatid]
    set last_vis_id [db eval {SELECT MAX(id) FROM visitors WHERE \
	nick = $nick AND grp = $group}]
    set ts [clock seconds]
    if {$last_vis_id != ""} {
	db eval {
	    UPDATE visitors
	    SET unavail=$ts
	    WHERE id = $last_vis_id
	}
    }
}

## Thanks to Alexey Smirnov <alexey.smirnov@gmx.com>
## Adapted by Serge Yudin.
proc bldjid2::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 } \
	{/bldquery }
    }

    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 bldjid2::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 {/bldquery*} $body]} {
	set cmd /bldquery
    } 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 9] "/bldquery "]} {
	lassign [::plugins::muc::parse_nick_reason $body 10] command body
    } 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 {
	}

	/unwatch {
	}

	/bldquery {
	    [namespace current]::bldquery $chatid $group $command $body
	}
    }
    return stop
}

proc bldjid2::bldquery {chatid group command body} {
    if {$command == "eval"} {
	if {[catch {eval $body} msg]} {
	    set header [::msgcat::mc "Your query failed. Here is its traceback:"]
	}
    } elseif {[lindex $command 0] == "save"} {
	set query_name [lreplace $command 0 0]
    } elseif {[lindex $command 0] == "get"} {
	set query_name [lreplace $command 0 0]
    } elseif {$command == "list"} {
    } else {
	set header [::msgcat::mc "Wrong subcommand. Available subcommands are:"]
	set msg [::msgcat::mc \
	"eval\nsave\nget\nlist\nSyntax:\n/bldquery subcommand \[arg\]\nyour Tcl script"]
    }
    [namespace current]::print_search_res $chatid $group $header $msg
}

proc bldjid2::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 bldjid2::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 bldjid2::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 bldjid2::amnesty {chatid attr val w f reason} {
    set reason [linsert $reason 0 amnesty]
    [namespace current]::request_list $attr $val $chatid $reason
    destroy $w
}

proc bldjid2::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 bldjid2::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 bldjid2::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 bldjid2::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 bldjid2::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 bldjid2::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 bldjid2::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 bldjid2::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 bldjid2::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 bldjid2::help {chatid group} {
    set header [::msgcat::mc "Short usage examples for Bldjid2 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 -> Bldjid2\
    \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 bldjid2::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 bldjid2::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 ""
    }
    set myvos ""
    return "\n$n:\t$timestamp $s $nick $s $jid$myroleaff$myvos$myvhash$myroom"
}

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

# These two next procs are taken from snoopstatus.tcl and modified a bit.
proc bldjid2::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 bldjid2::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 bldjid2::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 bldjid2::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 bldjid2::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 Bldjid2 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 bldjid2::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 bldjid2::log_msg {msg} {
    variable options
    set log $options(log_file)
    return
    if { $log eq "" } {
	return
    }
    if { [catch {open $log a} fd] } {
	debugmsg bldjid2 \
	"ERROR: cannot open log file."
	return
    }
    puts $fd "$msg"
    close $fd
}

proc bldjid2::request_iq_ver {xlib chatid group nick node part_list full_list 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 \
	$xlib $chatid $group $nick $node $part_list $full_list $my_id]
}

proc bldjid2::parse_info_iqversion {xlib chatid group nick node part_list full_list my_id res child} {
    variable options
    if {![winfo exists [chat::chat_win $chatid]]} {
	return
    }
    set jid $group/$nick
    if {![cequal $res ok]} {
	set cli " "
	set ver " "
	set os " "
    }

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

    userinfo::parse_iqversion_item $jid $subels
    if {[info exists userinfo::userinfo(clientname,$jid)] && \
     ![cequal $userinfo::userinfo(clientname,$jid) ""]} {
	append cli " $userinfo::userinfo(clientname,$jid)"
	set cli [string trim $cli]
	if {[string equal \*NO\ CLIENT\* $cli] && $node != ""} {
	    set cli $node
	}
    } else {
	set cli "NO INFO"
    }
    set cli_id [db eval {SELECT id FROM client WHERE cli=$cli}]
    if {[string equal $cli_id ""]} {
	db eval {
	    INSERT INTO client
	    VALUES(NULL, $cli)
	}
	set cli_id [db eval {SELECT last_insert_rowid();}]
    }
    if {[info exists userinfo::userinfo(clientversion,$jid)] && \
     ![cequal $userinfo::userinfo(clientversion,$jid) ""]} {
	append ver " $userinfo::userinfo(clientversion,$jid)"
	set ver [string trim $ver]
	set ver [join [split $ver \n] ""]
    } else {
	set ver "NO INFO"
    }
    set ver_id [db eval {SELECT id FROM version WHERE ver=$ver}]
    if {[string equal $ver_id ""]} {
	db eval {
	    INSERT INTO version
	    VALUES(NULL, $ver)
	}
	set ver_id [db eval {SELECT last_insert_rowid();}]
    }
    if {[info exists userinfo::userinfo(os,$jid)] && \
     ![cequal $userinfo::userinfo(os,$jid) ""]} {
	append os " $userinfo::userinfo(os,$jid)"
	set os [string trim $os]
	set os [join [split $os \n] ""]
    } else {
	set os "NO INFO"
    }
    set os_id [db eval {SELECT id FROM osys WHERE os=$os}]
    if {[string equal $os_id ""]} {
	db eval {
	    INSERT INTO osys
	    VALUES(NULL, $os)
	}
	set os_id [db eval {SELECT last_insert_rowid();}]
    }
    lappend part_list $cli_id $ver_id $os_id
    set part_vis_h [[namespace current]::hash \
    [[namespace current]::get_string4hash $part_list]]
    lappend full_list $cli_id $ver_id $os_id
    set full_vis_h [[namespace current]::hash \
    [[namespace current]::get_string4hash $full_list]]
# UPDATE cli_id, ver_id, AND os_id FIELDS IN my_id ROW OF THE visitors TABLE
    db eval {
	UPDATE visitors
	SET cli_id=$cli_id, ver_id=$ver_id, os_id=$os_id, \
	part_vis_h=$part_vis_h, full_vis_h=$full_vis_h
	WHERE id=$my_id
    }
    [namespace current]::request_iq_time $xlib $chatid $group $nick \
    $cli_id $ver_id $os_id $part_list $full_list $my_id
}

proc bldjid2::request_iq_time {xlib chatid group nick cli_id ver_id os_id part_list full_list my_id} {
    ::xmpp::sendIQ $xlib get \
    -query [::xmpp::xml::create query \
    -xmlns jabber:iq:time] \
    -to $group/$nick \
    -command [list [namespace current]::parse_info_iqtime \
	$chatid $group $nick $cli_id $ver_id $os_id $part_list $full_list $my_id]
}

proc bldjid2::parse_info_iqtime {chatid group nick cli_id ver_id os_id part_list full_list my_id res child} {
    if {![winfo exists [chat::chat_win $chatid]]} {
	return
    }
    set jid $group/$nick
    if {![cequal $res ok]} {
	set tz " "
    }

    ::xmpp::xml::split $child tag xmlns attrs cdata subels
    userinfo::parse_iqtime_item $jid $subels
    if {[info exists userinfo::userinfo(tz,$jid)] && \
     ![cequal $userinfo::userinfo(tz,$jid) ""]} {
	append tz " $userinfo::userinfo(tz,$jid)"
	set tz [string trim $tz]
    } else {
	set tz "NO INFO"
    }
    set tz_id [db eval {SELECT id FROM timezone WHERE tz=$tz}]
    if {[string equal $tz_id ""]} {
	db eval {
	    INSERT INTO timezone
	    VALUES(NULL, $tz)
	}
	set tz_id [db eval {SELECT last_insert_rowid();}]
    }
    lappend part_list $tz_id
    set part_vis_h [[namespace current]::hash \
    [[namespace current]::get_string4hash $part_list]]
    lappend full_list $tz_id
    set full_vis_h [[namespace current]::hash \
    [[namespace current]::get_string4hash $full_list]]
# UPDATE tz_id FIELD IN my_id ROW OF THE visitors TABLE
    db eval {
	UPDATE visitors
	SET tz_id=$tz_id, part_vis_h=$part_vis_h, full_vis_h=$full_vis_h
	WHERE id=$my_id
    }
}

# Taken from Clienticons plugin, thanks to Jet <megaxbit@xmpp.ru>!
proc bldjid2::presence_data_collector {xlib jid type x args} {
    variable presence_data
    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 presence_data(jidnodes,$nick,$chatid) \
		[::xmpp::xml::getAttr $attrs node]
		set presence_data(jidver,$nick,$chatid) \
		[::xmpp::xml::getAttr $attrs ver]
	    }
	    if {[::xmpp::xml::getAttr $xdata x] == "jabber:x:avatar"} {
		::xmpp::xml::split $xdata tag xmlns attrs cdata subels
		set presence_data(av_hash,$nick,$chatid) [lindex [lindex $subels 0] 4]
	    }
	    if {[::xmpp::xml::getAttr $xdata x] == "vcard-temp:x:update"} {
		::xmpp::xml::split $xdata tag xmlns attrs cdata subels
		set presence_data(photo_hash,$nick,$chatid) [lindex [lindex $subels 0] 4]
	    }
	}
	foreach {attr val} $args {
	    switch -- $attr {
		-x {
		    if {[lindex $val 0] == "ver"} {
			set bv [lindex $val 1]_
		    } else {
			set bv ""
		    }
		    set presence_data(bmod_version,$nick,$chatid) $bv
		}
		-lang {
		    set presence_data(presence_lang,$nick,$chatid) $val
		}
		-id {
		    set presence_data(presence_id,$nick,$chatid) $val
		}
	    }
	}
	return
    }
}

# Taken from muc.tcl
proc bldjid2::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 bldjid2::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 bldjid2::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 bldjid2::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
    }
}

proc bldjid2::get_string4hash {list} {
    set str ""
    foreach el $list {
	append str [encoding convertto utf-8 $el]
	append str <
    }
    return $str
}

proc bldjid2::hash {string} {
    set binstr [encoding convertto utf-8 $string]<
    if {[catch {::md5::md5 -hex $binstr} hex]} {
	set hex [::md5::md5 $binstr]
    }
    set binhash [binary format H32 $hex]
    return [base64::encode $binhash]
}


# vim:ts=8:sw=4:sts=4:noet
