if (word(2 $loadinfo()) != [pf]) {
	load -pf $word(1 $loadinfo());
	return;
};

if (J !~ [EPIC5*]) {
	xecho -b ERROR: userlist: load: EPIC5 is required;
	return;
};

package userlist;

## Userlist script for EPIC5.
## Written by zlonix@efnet, public domain.
##
## Version: 1.0 (October, 2014)
##  - Initial roll-out
##
## Version: 1.1 (December, 2014)
##  - Check if joined person hasn't been opped already before we op
##    him (helps to not mode-flood chans on netjoins when servers op
##    people automatically)

## This script provides basic userlist functionality found in most
## modern bots.  It allows automatic mode setting (op, voice, kickban)
## and on-demand operation, based on remote user CTCP requests (op,
## voice, invite, unban). Authentication is based only on hostmasks,
## password support is not presented. The script requires addset and ban
## scripts from base EPIC5 distribution.
##
## Userlist (op, voice, etc) globally turned on or off by boolean /set
## userlist command.  Offensive actions (kickban for now) controlled by
## /set excludelist.
##
## Following aliases provides list management:
##
##   /adduser <user> <host> [[host] [host] ...] - add user and
##   corresponding hosts, script also checks that given host can be
##   assigned only to one user
##
##   /addflag <user> <chan> <flag> [[flag] [flag] ...] - add flags to
##   specified <user> on the <chan>, based on this flags script will
##   take automatic or on-demand actions, possible flags are:
##      autoop - auto op user on join (in random 5 sec interval;
##               implies access to 'op' flag)
##      autovoice - auto voice user on join (in random 5 sec interval
##                  implies access to 'voice' flag
##      op - op user after /ctcp <yournick> OP <chan>
##      voice - voice user after /ctcp <yournick> VOICE <chan>
##      unban - unban user after /ctcp <yournick> UNBAN <chan> (only
##              current user host is unbanned)
##      invite - invite user after /ctcp <yournick> INVITE <chan>
##      kickban - auto kickban user on join (ban mask is hardcoded to
##                *@domain.tld - this is type '2' from mask() help page,
##                works only if /set excludelist is on)
##      friend - combination of op, voice, unban and invite flags
##
##   /showuser [user] - show information about the [user] or about all
##   users if no arguments are given
##
##   /saveuser <file> - save current userlist to a file, file is deleted
##   first if it's exist, you must load this file (not with PF loader)
##   in your ~/.ircrc to preserve your userlist between client runs
##
##   /delhost <user> <host> [[host] [host] ...] - delete hosts from user
##
##   /delflag <user> <chan> <flag> [[flag] [flag] ...] - delete flags
##   from user on the <chan>
##   
##   /deluser <user> [[user] [user] ...] - delete users from userlist,
##   all corresponding information is deleted (hosts, flags)
##
##   /dumpuser - clears userlist (primary usage - in userlist config
##   file)
##
## Examples:
##
##   /adduser testnick test@nick.com foo@example.com
##   /addflag testnick #epic autoop unban
##   /addflag testnick #epic2 voice
##
##   /adduser badnick *@badhost.com
##   /addflag badnick #epic kickban
##
## Bugs: user name maynot contain things which can't be used in variable
##       name, for example, |, although it doesn't matter much, because
##       all authentication is done by hostmask, not by nick

load addset;
load ban;

alias adduser (user, host, ...) {
	if (@user && @host) {
		fe ($host $*) hh {
			@ :badhost = 0;

			foreach userlist uu {
				if (findw($hh $userlist[$uu]) > -1) {
					if (uu == user) {
						xecho -c -b adduser: host $hh already presented for $user;
					} else {
						xecho -c -b adduser: conflict - $hh already added for user $tolower($uu);
					};
					@ :badhost = 1;
					break;
				};
			};
			if (!badhost) {
				push ::userlist[$user] $hh;
				xecho -s -c -b Added host [$hh] for user $user;
			};
		};
	} else {
		xecho -c -b Usage: /adduser <user> <host> [[host] [host] ...];
	};
};

alias addflag (user, chan, flag, ...) {
	if (@user && @chan && @flag) {
		if (!userlist[$user]) {
			xecho -c -b addflag: unknown user $user;
			return;
		};
		if (!ischannel($chan)) {
			xecho -c -b addflag: $chan is not a channel;
			return;
		};
		fe ($flag $*) ff {
			if (findw($ff autoop autovoice op voice invite unban kickban friend) == -1) {
				xecho -c -b addflag: unknown flag $ff;
				continue;
			};
			if (findw($ff $userlist[$user][$encode($chan)]) == -1) {
				push ::userlist[$user][$encode($chan)] $ff;
				push :tmp $ff;
			} else {
				xecho -c -b addflag: flag $ff already added for $user on $chan;
			};
		};
		if (@tmp) {
			xecho -s -c -b Added flags [$tmp] for user $user on channel $chan;
		};
	} else {
		xecho -c -b Usage: /addflag <user> <chan> <flag> [[flag] [flag] ...];
	};
};

alias delflag (user, chan, flag, ...) {
	if (@user && @chan && @flag) {
		if (!userlist[$user]) {
			xecho -c -b delflag: unknown user $user;
			return;
		};
		if (!ischannel($chan)) {
			xecho -c -b delflag: $chan is not a channel;
			return;
		};
		fe ($flag $*) ff {
			if (findw($ff autoop autovoice op voice invite unban protect kickban) == -1) {
				xecho -c -b addflag: unknown flag $ff;
				continue;
			};
			if (findw($ff $userlist[$user][$encode($chan)]) > -1) {
				@ ::userlist[$user][$encode($chan)] = remw($ff $::userlist[$user][$encode($chan)]);
				push :tmp $ff;
			} else {
				xecho -c -b delflag: flag $ff doesn't  exist for user $user on $chan;
			};
		};
		if (@tmp) {
			xecho -c -b Removed flags [$tmp] for user $user on channel $chan;
		};
	} else {
		xecho -c -b Usage: /delfalg <user> <chan> <flag> [[flag] [flag] ...];
	};
};

alias deluser (user, ...) {
	if (@user) {
		fe ($user $*) uu {
			if (userlist[$uu]) {
				foreach userlist[$uu] cc {
					^assign -userlist[$uu][$cc];
				};
				^assign -userlist[$user];
			} else {
				xecho -c -b deluser: user $uu doesn't exist;
			};
		};
	} else {
		xecho -c -b Usage: /deluser <user> [[user] [user] ...];
	};
};

alias delhost (user, host, ...) {
	if (@user && @host) {
		if (!userlist[$user]) {
			xecho -c -b delhost: user $user doesn't exist;
			return;
		};
		fe ($host $*) hh {
			if (findw($hh $userlist[$user]) > -1) {
				@ ::userlist[$user] = remw($hh $::userlist[$user]);
				push :tmp $hh;
			} else {
				xecho -c -b delhost: host $hh doesn't belong to user $user;
			};
		};
		if (@tmp) {
			xecho -c -b Removed hosts [$tmp] for user $user;
		};
	} else {
		xecho -c -b Usage: /delhost <user> <host> [[host] [host] ...];
	};
};

alias showuser (user) {
	if (@user) {
		showuser.echo $user;
	} else {
		foreach userlist ii {
			showuser.echo $ii;
		};
	};
};

alias showuser.echo (user) {
	if (@user) {
		xecho -b User: $tolower($user);
		xecho -b   Hostmasks: $userlist[$user];
		foreach userlist.${user} ii {
			xecho -b   Channel: $tolower($decode($ii)) - $sort($userlist[$user][$ii]);
		};
	};
};

alias saveuser (file) {
	if (@file) {
		if (fexist($file) == 1 && unlink($file) > 0) {
			xecho -c -b saveuser: can't unlink existing $file before writing - abort;
			return;
		};
		@ :fd = open($file W);
		if (isfilevalid($fd) == 1) {
			@ write($fd ^dumpuser);
			foreach userlist uu {
				@ :data = [^adduser $tolower($uu) $userlist[$uu]];
				@ write($fd $data);
			};
			foreach userlist uu {
				foreach userlist.${uu} cc {
					@ :data = [^addflag $tolower($uu) $decode($cc) $userlist[$uu][$cc]];
					@ write($fd $data);
				};
			};
			@ close($fd);
			xecho -c -b Userlist saved to $file;
		} else {
			xecho -c -b saveuser: isfilevalid() not equal 1;
		};
	} else {
		xecho -c -b Usage: /saveuser <file>;
	};
};

alias userflags (host, chan, void) {
	foreach userlist uu {
		if ((:idx = rmatch($host $userlist[$uu])) > 0) {
			@ :idx--;

			push :users $uu;
			push :hosts $word($idx $userlist[$uu]);
		};
	};
	if ((:idx = rmatch($host $hosts)) > 0) {
		@ :idx--;

		return $userlist[$word($idx $users)][$encode($chan)];
	} else {
		return;
	};
};

alias isfriend (nick, chan, void) {
	return ${findw(friend $userflags($userhost($nick) $chan)) > -1};
};

alias friends (chan, void) {
	fe ($strip(?.%+@ $channel($chan))) uu {
		if (isfriend($uu $chan)) {
			push :tmp $uu;
		};
	};

	return $tmp;
};

alias dumpuser (void) {
	foreach userlist uu {
		deluser $uu;
	};
	^assign -userlist;
};

alias userlist.doop (chan, nick, void) {
	if (!ischanop($nick $chan) && ischanop($servernick() $chan)) {
		mode $chan +o $nick;
	};
};

alias userlist.dovoice (chan, nick, void) {
	if (!ischanvoice($nick $chan) && ischanop($servernick() $chan)) {
		mode $chan +v $nick;
	};
};

on #-join 100 * {
	switch ($userflags($userhost() $1)) {
		(*kickban*) {
			if (excludelist == [on]) {
				mode $1 -o+b $0 $mask(2 $userhost());
				kick $1 $0 user has been banned;
			};
		};
		(*autoop*) {
			if (userlist == [on]) {
				timer 5 userlist.doop $1 $0;
			};
		};
		(*autovoice*) {
			if (userlist == [on]) {
				timer 5 userlist.dovoice $1 $0;
			};
		};
	};
};

on #-ctcp 100 '% $$servernick() \\\\[op voice invite unban\\\\] %' {
	if (userlist != [on]) {
		return;
	};

	@ :uf = userflags($userhost() $3);

	if ((findw($2 $uf) > -1 || findw(friend $uf) > -1) && ischanop($servernick() $3)) {
		switch ($2) {
			(*op) {
				timer 5 userlist.doop $3 $0;
			};
			(*voice) {
				timer 5 userlist.dovoice $3 $0;
			};
			(invite) {
				invite $0 $3;
			};
			(unban) {
				unban $3 $0;
				notice $0 You've been unbanned on channel $3;
			};
		};
	};
};
addset userlist bool;
addset excludelist bool;
