Login and user system

From Scriptwiki
Jump to: navigation, search

A user system with accesslevels and user control.
The main idea of this script is to identify users through a login, not like mirc's default accesslevels, by nick/ident/host or some of those combinations.
Here it doesn't matter what nick/ident/host the user has, he just logs into the bot.

Commands:

  • /loadusers = makes the users and login tables, and loads saved user information.
  • /saveusers = saves user information to users.hsh and frees users and login tables.
  • /adduser = adds a new user (/adduser <username> <password>
  • /deluser = deletes an existing user /deluser <username>)
  • /userlist = gives you a list of users (/userlist <-all|-active> -all = all of the users, -active = list only logged in users)
  • /edituser = edits users information (/edituser <-pass|-username|-addlevel|-remlevel|-help> <username> <parameters>)
  • /logoutusers = logs out all users from a given channel (/logoutusers #chan, used when you part/get kicked)

$adduser, $deluser and $edituser aliases can be used as an identifier, they return the message whether command was succesfull, or errormessage if input wasn't correct

Identifiers:

  • $checklevel = checks if the user has sufficient accesslevel, returns $true/$false ($checklevel($address,$chan,master))
  • $getlevel = return the users level, numbered/named ($getlevel($address,$chan|Global))
  • $getuser = returns username by given address ($getuser($address))

A little more explanation on how this works:
The user can have an acceslevels on a channel, and a global accesslevel, when checking for a level on certain channel, and it's insufficient, then the global level is checked.
ie. you have a command !op, which requires atleast level 10 to use it, $checklevel($address,$chan,10) is then used, if the user has accesslevel of 5 on that channel, but he has a Global level of 15, he can use the command.
You can also check only for a global level by doing $checklevel($address,global,10), and if you only want to know, if the user is logged in, you can use $checklevel($address).

If you want to compare the exact level of the user, use $getlevel($address,$chan) ($chan or "global") in an if like: if ($getlevel($address,$chan) == Master) { notice $nick yes, you are a master on $chan }
If you wish to use named levels, like Master, Owner, etc. you'll need to add those levels with corresponding numbered level into the cuser alias, see the source for more info.

I added events for user control to the examples part, so you can add the script to your bot and use the commands via query/channel with !command.

;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; Usersystem with accesslevels and user control.
;; Contact: Cail @ QuakeNet -> #help.script
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;

alias loadusers {
 ; check if 'users' and 'login' tables already exist, if not, create them
 if (!$hget(users)) { hmake users 100 }
 if (!$hget(login)) { hmake login 100 }

 ; check if there is a file 'users.hsh' (saved user information), if we have it, empty users-table and load the saved info
 if ($is_file(users.hsh)) { hdel -w users * | hload users users.hsh }
}

alias saveusers {
 ; check if 'users' and 'login' tables even exist, if so, they can be saved and freed
 if ($hget(users)) { hsave users users.hsh | .hfree users }
 if ($hget(login)) { hfree login }
}

alias -l logoutusers {
 ; loop through a given channel ($1 = channel) and logout everyone who is logged in

 var %x = 1 
 while ($nick($1,%x)) { 
   var %addr = $v1 

   ; check the login table for a matching address with $ial().addr, and if he's not on any other same channels as you
   if ($hget(login,$ial(%addr).addr)) && (!$comchan(%addr,2)) { hdel login %addr } 
   inc %x 
 }
}

alias cuser {
 ; return corresponding accesslevel for named levels, needed for comparison

 if ($1 == owner) { return 200 }
 elseif ($1 == master) { return 150 }
 elseif ($1 == test) { return 100 }
 else { return $1 }
}

alias checklevel {
 ; if $2 ('#channel' or 'global') isn't given, check if the user is logged in
 if (!$2) { return $iif($hget(login,$1),$true,$false) }

 ; fetch the username to %user from the login table by $1 (given address),
 ; %lvl_chan and %lvl_global will get their values from the users-table, converted to a numeric with $cuser
 ; $iif gives them value 0 if they don't exist (comparison won't work with $null)
 var %user = $hget(login,$1)
 var %lvl_chan = $iif($cuser($hget(users,$+(%user,$chr(7),$2))),$v1,0)
 var %lvl_global = $iif($cuser($hget(users,$+(%user,$chr(7),global))),$v1,0)

 ; inner $iif checks which one is greater, channel level or global level, and compares it to given level ($3)
 return $iif($iif(%lvl_global > %lvl_chan,%lvl_global,%lvl_chan) >= $cuser($3),$true,$false)
}

alias getlevel {
 ; return the named level by given address
 return $hget(users,$+($hget(login,$1),$chr(7),$2))
}

alias getuser { 
 ; return username by given address
 return $hget(login,$1) 
}

alias adduser {
 ; errorchecking
 ; check if the users-table exist
 if (!$hget(users)) { var %error = You need to load the usertable first: /loadusers }
 ; check if there's enough parameters
 if (!$2) { var %error = Insufficient parameters, usage: /adduser <username> <password> }
 ; check if given username doesn't exist
 if ($hget(users,$1)) { var %error = That username is already taken! }
 ; validate the username and password
 if (!$regex($+($1,$2),/^([][A-Za-z\\^`{|}-][][\w\\^`{|}-]*)$/)) { var %error = Incorrect username. }

 ; if we encountered an error return the error msg or echo it, depending of if it was called as an identifier or not
 ; else say that everything's fine
 if (%error) { echo -ag %error | return %error } 

 ; everything was fine if we got here, add the user into users table and save the table
 hadd users $1 $2
 hsave users users.hsh 
 echo -ag User $1 added.
 return User $1 added.
}

alias edituser {
 ; errorchecking
 ; check if valid parameters are given ($1 = switch, $2 = username, $3- = parameters)
 if (!$istok(-pass -addlevel -remlevel -username -help,$1,32)) || (!$3) { 
   var %error Usage: /edituser <-pass|-addlevel|-remlevel|-username|-help> <username> <parameters>
 }
 ; check if the user exists
 elseif (!$hget(users,$2)) { var %error No such user. }

 ; echo/return errors if any, else carry on
 if (%error) { echo -ag %error | return %error }

 ; get the address from the login-table, and check if the address is found from IAL, if so, we can msg the user with the changes made
 var %nick = $ial($+(*,$hfind(login,$2,1).data),1).nick

 ; -pass: changes the users password
 if ($1 == -pass) {
   ; check the validity of the password
   if (!$regex($3,/^([][A-Za-z\\^`{|}-][][\w\\^`{|}-]*)$/)) { var %error Incorrect password. }

   ; check for errors
   if (%error) { echo -ag %error | return %error }

   ; everything's fine, add the new password to the users table, echo, and msg %nick (if found)
   hadd users $2 $3 
   if (%nick) { .notice $v1 Your password has been changed to $3 }
   echo -ag Changed $2 $+ 's password to $3 
   return Changed $2 $+ 's password to $3
 }

 ; -addlevel: add access level to user, for #channel or Global
 elseif ($1 == -addlevel) {
   ; here we have more parameters, so we need to check that there is $4
   if ($4 == $null) { var %error Usage: /edituser -addlevel username #channel|global accesslevel }

   ; check for errors
   if (%error) { echo -ag %error | return %error }

   ; add a new item into users-table (username7#channel) that 7 in there is $chr(7), we use it to separate username and channel in the item name
   ; then again echo and msg %nick if possible
   hadd users $+($2,$chr(7),$3) $4
   if (%nick) { .notice $v1 Your $3 accesslevel has been changed to $4 $+ . }
   echo -ag Changed $2 $+ 's level to > $3 $+ : $4
   return Changed $2 $+ 's level to > $3 $+ : $4
 }

 ; -remlevel: remove a users level
 elseif ($1 == -remlevel) {
   ; check that the user has accesslevels in $3 (given channel/Global)
   if (!$hget(users,$+($2,$chr(7),$3))) { var %error = User $2 doesn't have accesslevel in $3 }

   ; check for errors
   if (%error) { echo -ag %error | return %error }

   ; hdel the item from users-table, echo, and msg %nick if possible
   hdel users $+($2,$chr(7),$3) 
   if (%nick) { .notice $v1 Your accesslevel in $3 has been removed. }
   echo -ag Removed $2 $+ 's accesslevel from $3
   return Removed $2 $+ 's accesslevel from $3
 }

 ; -username: change the username of someone (this is case-insensitive, so you cannot change ie. Cail -> cail)
 elseif ($1 == -username) {
   ; check that the new username isn't already taken, and validate the new username
   if ($hget(users,$3)) { var %error = That username is already taken! }
   if (!$regex($3,/^([][A-Za-z\\^`{|}-][][\w\\^`{|}-]*)$/)) { var %error = Incorrect username. }

   ; check for errors
   if (%error) { echo -ag %error | return %error }

   ; we need to store the address to a variables, 'cause we will be deleting the old item from the login-table
   var %pass = $hget(users,$2)
   var %address = $hfind(login,$2,1).data

   ; add the new username with the old password, and delete the old item
   hadd users $3 $hget(users,$2)
   hdel users $2 

   ; update all the access levels for the new username, %y = the number of accesslevel items
   var %y = $hfind(users,$+($2,$chr(7),*),0,w)
   while (%y) {
     ; use $hfind to get the old item name
     var %item = $hfind(users,$+($2,$chr(7),*),%y,w)

     ; add the new itemname with the old value, and delete the old item
     hadd users $+($3,$chr(7),$gettok(%item,2-,7)) $hget(users,%item)
     hdel users %item
     dec %y
   }

   ; change the username in the login-table, if the user is logged in, and msg him with the changes
   if (%address) { hadd login %address $3 }
   if (%nick) { .notice $v1 Your username has been changed to $3 $+ . }
   echo -ag Changed $2 $+ 's username to $3
   return Changed $2 $+ 's username to $3
 }

 ; -help: echo the syntax of /edituser
 elseif ($1 == -help) {
   echo -ag -pass -> /edituser -pass <username> <new_password>
   echo -ag -addlevel -> /edituser -addlevel <username> <#channel|global> <level>
   echo -ag -remlevel -> /edituser -remlevel <username> <#channel|global>
   echo -ag -username -> /edituser -username <username> <new_username>
   echo -ag If level is a named level, make sure you have added it into the cuser -alias.
 }

 ; we have made some changes to the users-table, save it now
 hsave users users.hsh
}

alias deluser {
 ; check that given username exists
 if (!$hget(users,$1)) { var %error No such user. }

 ; check for errors
 if (%error) { echo -ag %error | return %error }

 ; delete the username from the userstable and remove all of his accesslevels, and echo the progress
 ; and if he is logged in, remove him from the login-table
 hdel users $1
 hdel -w users $+($1,$chr(7),*)
 if ($hfind(login,12,1).data) { hdel login $v1 }
 echo -ag Removed user $1
 return Removed user $1

 ; again, we made changes to the users-table, save it
 hsave users users.hsh
}

alias userlist {
 ; check that given input is correct
 if (!$istok(-all -active,$1,32)) { echo -ag Usage: /userlist <-all|-active> | return }

 ; make variable %active $true/$false depending on if we're looking for active users
 var %active = $iif($1 == -active,$true,$false)

 ; echo an empty line and "listing blaa blaa"
 echo -ag $chr(160)
 echo -ag --- Listing $remove($1,-) users:

 ; the regular expression in the $hfind finds all the items, that doesn't have $chr(7) in the itemname (we don't want to mix accesslevel items with username items)
 var %x = 1 
 while ($hfind(users,/^[^\x07]+$/i,%x,r)) { 
   ; put the username into a variable, if the user is logged in, %address will hold his address, assign %levels variable to $null at the start
   var %user = $v1
   var %address = $hfind(login,%user,1).data
   var %levels = $null

   ; if we are looking for active users %active will now be $true, and if there's no %address, the user isn't logged in, so /continue and keep looking for users
   if (%active) && (!%address) { inc %x | continue }

   ; echo username and address/"not logged", $base() here will zero-pad the number (2 becomes 02, looks better)
   echo -ag $chr(31) $+ $base(%x,10,10,2) $+ : %user ( $+ $iif(%address,%address,not logged) $+ ) $chr(31) 

   ; now that we have a user, loop through all of his accesslevels (now we want to find everyitem starting with username7, where the 7 is again $chr(7))
   var %y = 1
   while ($hfind(users,$+(%user,$chr(7),*),%y,w)) {
     ; put the levels into %levels variable, so we can echo all of them on one line (well put $chr(7) here to separate the "#channel: level" from "#channel2: level2" so we can sort them later)
     var %levels = $gettok($v1,2-,7) $+ : $hget(users,$v1) $+ $chr(7) $+ %levels
     inc %y
   }

   ; so we have all the levels the user has in %levels, now $sorttok so we get the Global-level first, and replace that $chr(7) with spaces and -
   ; we would get a line like: Global: Master - #channel: Owner etc.
   echo -ag $str($chr(160),3) Levels: $replace($sorttok(%levels,7,r),$chr(7),$+($chr(32),-,$chr(32)))
   inc %x 
 }

 ; echo "end of" and an empty line
 echo -ag --- End of users.
 echo -ag $chr(160)
}

; EVENTS

on *:start:{ loadusers }
on *:exit:{ saveusers }

on !*:quit:{ 
 ; if the user that quit, was logged in, remove him from the login table
 if ($hget(login,$address)) { hdel login $address } 
}

on *:part:#:{ 
 ; if it is you, who parts the channel, logout everyone in that channel (if they're not on some other channels, where you are)
 ; else check if the user that quit, is logged in (and is not on any other chans with you), log him out

 if ($nick == $me) { logoutusers # }
 elseif ($hget(login,$address)) && (!$comchan($nick,2)) { hdel login $address } 
}

on *:kick:#:{ 
 ; if it is you, who got kicked, logout everyone in that channel (if they're not on some other channels, where you are)
 ; else check if the user that got kicked, is logged in (and is not on any other chans with you), log him out

 if ($knick == $me) { logoutusers # }
 elseif ($hget(login,$address)) && (!$comchan($knick,2)) { hdel login $address } 
}

on ^*:open:?:login & &:{
 ; check if user is already logged in, if so, tell him that
 if ($hget(login,$address)) {
   .notice $nick You are already logged in!
 }
 ; check that the user is on atleast one channel with you
 elseif (!$comchan($nick,0)) { 
   .notice $nick You need to be on atleast one same channel as i am to login. 
 }
 ; if the login information is correct (check that item $2 has the right password $3), add the user into login-table
 elseif ($hget(users,$2) == $3) {
   .notice $nick succesfully logged in
   hadd login $address $2
 }
 ; else the login information is incorrect
 else { 
   .notice $nick Wrong username/password.
 }
 haltdef
}

Exaples of usage

on *:text:!adduser & &:*:{
 if ($getlevel($address,global) == Owner) { .notice $nick $adduser($2,$3) }
 else { .notice $nick You need to be an owner to use this command }
}

on *:text:!edituser & & *:*:{
 if ($getlevel($address,global) == Owner) { .notice $nick $edituser($2,$3,$4,$5) }
 else { .notice $nick You need to be an owner to use this command }
}

on *:text:!deluser &:*:{
 if ($getlevel($address,global) == Owner) { .notice $nick $deluser($2) }
 else { .notice $nick You need to be an owner to use this command }
}

on *:text:!opme:*:{ 
 ; we'll use $checklevel to determine if the user has atleast accesslevel of Master in $chan (or higher Global level)
 if ($checklevel($address,$chan,master)) { mode $chan +o $nick }
 else { .notice $nick You have to be atleast master to use this command. }
}

on *:text:!deopme:*:{ 
 ; and here the same, the user needs to have atleast accesslevel of 'test'
 if ($checklevel($address,$chan,test)) { mode $chan -o $nick }
 else { .notice $nick You have to be atleast at test level to use this command. }
}

on *:text:!whoami:*:{ 
 ; here we only check that is the user logged in, no other parameters given to $checklevel but the $address
 if ($checklevel($address)) { 
   ; $getuser returns the username of the user (username isn't the same as the nick)
   ; $getlevel returns the named/numbered level of the user on $chan or 'Global'

   .msg $chan You are known as $getuser($address) $+ , you have accesslevel of $iif( $getlevel($address,$chan) ,$v1,0) on $chan
   .msg $chan Your global level is $getlevel($address,Global) 
 }
 else { .notice $nick You need to login to use this command. }
}