--- /dev/null
+require("lib/quakenet")
+require("lib/missing")
+require("lib/schedule")
+require("lib/serialise")
+require("lib/country")
+require("lib/db")
+require("lib/socket")
+require("lib/json")
+require("lib/class")
+
+local LASTERROR = 0
+local NOTREPORTING = false
+
+function scripterror(err)
+ local cerror = os.time()
+ if cerror > LASTERROR then
+ LASTERROR = cerror
+ NOTREPORTING = false
+ end
+
+ if not NOTREPORTING then
+ chanmsgn(traceback(err, 2))
+
+ LASTERROR = LASTERROR + 1
+ if LASTERROR > cerror + 10 then
+ NOTREPORTING = true
+ chanmsg("Too many errors for this script -- no longer reporting")
+ end
+ end
+end
+
+math.randomseed(os.time())
--- /dev/null
+-- class.lua
+-- Compatible with Lua 5.1 (not 5.0).
+function class(base,ctor)
+ local c = {} -- a new class instance
+ if not ctor and type(base) == 'function' then
+ ctor = base
+ base = nil
+ elseif type(base) == 'table' then
+ -- our new class is a shallow copy of the base class!
+ for i,v in pairs(base) do
+ c[i] = v
+ end
+ c._base = base
+ end
+ -- the class will be the metatable for all its objects,
+ -- and they will look up their methods in it.
+ c.__index = c
+
+ -- expose a ctor which can be called by <classname>(<args>)
+ local mt = {}
+ mt.__call = function(class_tbl,...)
+ local obj = {}
+ setmetatable(obj,c)
+ if ctor then
+ ctor(obj,...)
+ else
+ -- make sure that any stuff from the base class is initialized!
+ if base and base.init then
+ base.init(obj,...)
+ end
+ end
+ return obj
+ end
+ c.init = ctor
+ c.is_a = function(self,klass)
+ local m = getmetatable(self)
+ while m do
+ if m == klass then return true end
+ m = m._base
+ end
+ return false
+ end
+ setmetatable(c,mt)
+ return c
+end
+
--- /dev/null
+local countries = { "--","AP","EU","AD","AE","AF","AG","AI","AL","AM","AN","AO","AQ","AR","AS","AT","AU","AW","AZ","BA","BB","BD","BE","BF","BG","BH","BI","BJ","BM","BN","BO","BR","BS","BT","BV","BW","BY","BZ","CA","CC","CD","CF","CG","CH","CI","CK","CL","CM","CN","CO","CR","CU","CV","CX","CY","CZ","DE","DJ","DK","DM","DO","DZ","EC","EE","EG","EH","ER","ES","ET","FI","FJ","FK","FM","FO","FR","FX","GA","GB","GD","GE","GF","GH","GI","GL","GM","GN","GP","GQ","GR","GS","GT","GU","GW","GY","HK","HM","HN","HR","HT","HU","ID","IE","IL","IN","IO","IQ","IR","IS","IT","JM","JO","JP","KE","KG","KH","KI","KM","KN","KP","KR","KW","KY","KZ","LA","LB","LC","LI","LK","LR","LS","LT","LU","LV","LY","MA","MC","MD","MG","MH","MK","ML","MM","MN","MO","MP","MQ","MR","MS","MT","MU","MV","MW","MX","MY","MZ","NA","NC","NE","NF","NG","NI","NL","NO","NP","NR","NU","NZ","OM","PA","PE","PF","PG","PH","PK","PL","PM","PN","PR","PS","PT","PW","PY","QA","RE","RO","RU","RW","SA","SB","SC","SD","SE","SG","SH","SI","SJ","SK","SL","SM","SN","SO","SR","ST","SV","SY","SZ","TC","TD","TF","TG","TH","TJ","TK","TM","TN","TO","TP","TR","TT","TV","TW","TZ","UA","UG","UM","US","UY","UZ","VA","VC","VE","VG","VI","VN","VU","WF","WS","YE","YT","YU","ZA","ZM","ZR","ZW","A1","A2","O1" }
+local countryhash
+
+function countrylookup(id)
+ return countries[id + 1]
+end
+
+function countrynamelookup(country)
+ if not countryhash then
+ local i = 0
+ countryhash = {}
+
+ for k, v in pairs(countries) do
+ countryhash[v] = k - 1
+ end
+ end
+
+ return countryhash[country]
+end
+
+function countryindex(country)
+ local i = 1
+
+ for v in pairs(countries) do
+ if countries[i] == country then
+ return i - 1
+ end
+ i = i + 1
+ end
+
+ return -1
+end
+
--- /dev/null
+function db_queryiter()
+ local c = db_numrows()
+ local i = -1
+ local f = db_numfields()
+ local gb = db_getvalue
+
+ return function()
+ i = i + 1
+ if i == c then
+ return nil
+ end
+
+ local t = {}
+ for j=0,f do
+ table.insert(t, gb(i, j))
+ end
+
+ return t
+ end
+end
+
+local realquery = db_query
+
+function db_query(x, fn, t)
+ if not fn then
+ realquery(x)
+ elseif not t then
+ realquery(x, fn, nil)
+ else
+ realquery(x, fn, t)
+ end
+end
--- /dev/null
+--require("lib/jit.opt").start()
+
--- /dev/null
+-----------------------------------------------------------------------------
+-- JSON4Lua: JSON encoding / decoding support for the Lua language.
+-- json Module.
+-- Author: Craig Mason-Jones
+-- Homepage: http://json.luaforge.net/
+-- Version: 0.9.20
+-- This module is released under the The GNU General Public License (GPL).
+-- Please see LICENCE.txt for details.
+--
+-- USAGE:
+-- This module exposes two functions:
+-- encode(o)
+-- Returns the table / string / boolean / number / nil / json.null value as a JSON-encoded string.
+-- decode(json_string)
+-- Returns a Lua object populated with the data encoded in the JSON string json_string.
+--
+-- REQUIREMENTS:
+-- compat-5.1 if using Lua 5.0
+--
+-- CHANGELOG
+-- 0.9.20 Introduction of local Lua functions for private functions (removed _ function prefix).
+-- Fixed Lua 5.1 compatibility issues.
+-- Introduced json.null to have null values in associative arrays.
+-- encode() performance improvement (more than 50%) through table.concat rather than ..
+-- Introduced decode ability to ignore /**/ comments in the JSON string.
+-- 0.9.10 Fix to array encoding / decoding to correctly manage nil/null values in arrays.
+-----------------------------------------------------------------------------
+
+-----------------------------------------------------------------------------
+-- Imports and dependencies
+-----------------------------------------------------------------------------
+local math = require('math')
+local string = require("string")
+local table = require("table")
+
+local base = _G
+
+-----------------------------------------------------------------------------
+-- Module declaration
+-----------------------------------------------------------------------------
+module("json")
+
+-- Public functions
+
+-- Private functions
+local decode_scanArray
+local decode_scanComment
+local decode_scanConstant
+local decode_scanNumber
+local decode_scanObject
+local decode_scanString
+local decode_scanWhitespace
+local encodeString
+local isArray
+local isEncodable
+
+-----------------------------------------------------------------------------
+-- PUBLIC FUNCTIONS
+-----------------------------------------------------------------------------
+--- Encodes an arbitrary Lua object / variable.
+-- @param v The Lua object / variable to be JSON encoded.
+-- @return String containing the JSON encoding in internal Lua string format (i.e. not unicode)
+function encode (v)
+ -- Handle nil values
+ if v==nil then
+ return "null"
+ end
+
+ local vtype = base.type(v)
+
+ -- Handle strings
+ if vtype=='string' then
+ return '"' .. encodeString(v) .. '"' -- Need to handle encoding in string
+ end
+
+ -- Handle booleans
+ if vtype=='number' or vtype=='boolean' then
+ return base.tostring(v)
+ end
+
+ -- Handle tables
+ if vtype=='table' then
+ local rval = {}
+ -- Consider arrays separately
+ local bArray, maxCount = isArray(v)
+ if bArray then
+ for i = 1,maxCount do
+ table.insert(rval, encode(v[i]))
+ end
+ else -- An object, not an array
+ for i,j in base.pairs(v) do
+ if isEncodable(i) and isEncodable(j) then
+ table.insert(rval, '"' .. encodeString(i) .. '":' .. encode(j))
+ end
+ end
+ end
+ if bArray then
+ return '[' .. table.concat(rval,',') ..']'
+ else
+ return '{' .. table.concat(rval,',') .. '}'
+ end
+ end
+
+ -- Handle null values
+ if vtype=='function' and v==null then
+ return 'null'
+ end
+
+ base.assert(false,'encode attempt to encode unsupported type ' .. vtype .. ':' .. base.tostring(v))
+end
+
+
+--- Decodes a JSON string and returns the decoded value as a Lua data structure / value.
+-- @param s The string to scan.
+-- @param [startPos] Optional starting position where the JSON string is located. Defaults to 1.
+-- @param Lua object, number The object that was scanned, as a Lua table / string / number / boolean or nil,
+-- and the position of the first character after
+-- the scanned JSON object.
+function decode(s, startPos)
+ startPos = startPos and startPos or 1
+ startPos = decode_scanWhitespace(s,startPos)
+ base.assert(startPos<=string.len(s), 'Unterminated JSON encoded object found at position in [' .. s .. ']')
+ local curChar = string.sub(s,startPos,startPos)
+ -- Object
+ if curChar=='{' then
+ return decode_scanObject(s,startPos)
+ end
+ -- Array
+ if curChar=='[' then
+ return decode_scanArray(s,startPos)
+ end
+ -- Number
+ if string.find("+-0123456789.e", curChar, 1, true) then
+ return decode_scanNumber(s,startPos)
+ end
+ -- String
+ if curChar==[["]] or curChar==[[']] then
+ return decode_scanString(s,startPos)
+ end
+ if string.sub(s,startPos,startPos+1)=='/*' then
+ return decode(s, decode_scanComment(s,startPos))
+ end
+ -- Otherwise, it must be a constant
+ return decode_scanConstant(s,startPos)
+end
+
+--- The null function allows one to specify a null value in an associative array (which is otherwise
+-- discarded if you set the value with 'nil' in Lua. Simply set t = { first=json.null }
+function null()
+ return null -- so json.null() will also return null ;-)
+end
+-----------------------------------------------------------------------------
+-- Internal, PRIVATE functions.
+-- Following a Python-like convention, I have prefixed all these 'PRIVATE'
+-- functions with an underscore.
+-----------------------------------------------------------------------------
+
+--- Scans an array from JSON into a Lua object
+-- startPos begins at the start of the array.
+-- Returns the array and the next starting position
+-- @param s The string being scanned.
+-- @param startPos The starting position for the scan.
+-- @return table, int The scanned array as a table, and the position of the next character to scan.
+function decode_scanArray(s,startPos)
+ local array = {} -- The return value
+ local stringLen = string.len(s)
+ base.assert(string.sub(s,startPos,startPos)=='[','decode_scanArray called but array does not start at position ' .. startPos .. ' in string:\n'..s )
+ startPos = startPos + 1
+ -- Infinite loop for array elements
+ repeat
+ startPos = decode_scanWhitespace(s,startPos)
+ base.assert(startPos<=stringLen,'JSON String ended unexpectedly scanning array.')
+ local curChar = string.sub(s,startPos,startPos)
+ if (curChar==']') then
+ return array, startPos+1
+ end
+ if (curChar==',') then
+ startPos = decode_scanWhitespace(s,startPos+1)
+ end
+ base.assert(startPos<=stringLen, 'JSON String ended unexpectedly scanning array.')
+ object, startPos = decode(s,startPos)
+ table.insert(array,object)
+ until false
+end
+
+--- Scans a comment and discards the comment.
+-- Returns the position of the next character following the comment.
+-- @param string s The JSON string to scan.
+-- @param int startPos The starting position of the comment
+function decode_scanComment(s, startPos)
+ base.assert( string.sub(s,startPos,startPos+1)=='/*', "decode_scanComment called but comment does not start at position " .. startPos)
+ local endPos = string.find(s,'*/',startPos+2)
+ base.assert(endPos~=nil, "Unterminated comment in string at " .. startPos)
+ return endPos+2
+end
+
+--- Scans for given constants: true, false or null
+-- Returns the appropriate Lua type, and the position of the next character to read.
+-- @param s The string being scanned.
+-- @param startPos The position in the string at which to start scanning.
+-- @return object, int The object (true, false or nil) and the position at which the next character should be
+-- scanned.
+function decode_scanConstant(s, startPos)
+ local consts = { ["true"] = true, ["false"] = false, ["null"] = nil }
+ local constNames = {"true","false","null"}
+
+ for i,k in base.pairs(constNames) do
+ --print ("[" .. string.sub(s,startPos, startPos + string.len(k) -1) .."]", k)
+ if string.sub(s,startPos, startPos + string.len(k) -1 )==k then
+ return consts[k], startPos + string.len(k)
+ end
+ end
+ base.assert(nil, 'Failed to scan constant from string ' .. s .. ' at starting position ' .. startPos)
+end
+
+--- Scans a number from the JSON encoded string.
+-- (in fact, also is able to scan numeric +- eqns, which is not
+-- in the JSON spec.)
+-- Returns the number, and the position of the next character
+-- after the number.
+-- @param s The string being scanned.
+-- @param startPos The position at which to start scanning.
+-- @return number, int The extracted number and the position of the next character to scan.
+function decode_scanNumber(s,startPos)
+ local endPos = startPos+1
+ local stringLen = string.len(s)
+ local acceptableChars = "+-0123456789.e"
+ while (string.find(acceptableChars, string.sub(s,endPos,endPos), 1, true)
+ and endPos<=stringLen
+ ) do
+ endPos = endPos + 1
+ end
+ local stringValue = 'return ' .. string.sub(s,startPos, endPos-1)
+ local stringEval = base.loadstring(stringValue)
+ base.assert(stringEval, 'Failed to scan number [ ' .. stringValue .. '] in JSON string at position ' .. startPos .. ' : ' .. endPos)
+ return stringEval(), endPos
+end
+
+--- Scans a JSON object into a Lua object.
+-- startPos begins at the start of the object.
+-- Returns the object and the next starting position.
+-- @param s The string being scanned.
+-- @param startPos The starting position of the scan.
+-- @return table, int The scanned object as a table and the position of the next character to scan.
+function decode_scanObject(s,startPos)
+ local object = {}
+ local stringLen = string.len(s)
+ local key, value
+ base.assert(string.sub(s,startPos,startPos)=='{','decode_scanObject called but object does not start at position ' .. startPos .. ' in string:\n' .. s)
+ startPos = startPos + 1
+ repeat
+ startPos = decode_scanWhitespace(s,startPos)
+ base.assert(startPos<=stringLen, 'JSON string ended unexpectedly while scanning object.')
+ local curChar = string.sub(s,startPos,startPos)
+ if (curChar=='}') then
+ return object,startPos+1
+ end
+ if (curChar==',') then
+ startPos = decode_scanWhitespace(s,startPos+1)
+ end
+ base.assert(startPos<=stringLen, 'JSON string ended unexpectedly scanning object.')
+ -- Scan the key
+ key, startPos = decode(s,startPos)
+ base.assert(startPos<=stringLen, 'JSON string ended unexpectedly searching for value of key ' .. key)
+ startPos = decode_scanWhitespace(s,startPos)
+ base.assert(startPos<=stringLen, 'JSON string ended unexpectedly searching for value of key ' .. key)
+ base.assert(string.sub(s,startPos,startPos)==':','JSON object key-value assignment mal-formed at ' .. startPos)
+ startPos = decode_scanWhitespace(s,startPos+1)
+ base.assert(startPos<=stringLen, 'JSON string ended unexpectedly searching for value of key ' .. key)
+ value, startPos = decode(s,startPos)
+ object[key]=value
+ until false -- infinite loop while key-value pairs are found
+end
+
+--- Scans a JSON string from the opening inverted comma or single quote to the
+-- end of the string.
+-- Returns the string extracted as a Lua string,
+-- and the position of the next non-string character
+-- (after the closing inverted comma or single quote).
+-- @param s The string being scanned.
+-- @param startPos The starting position of the scan.
+-- @return string, int The extracted string as a Lua string, and the next character to parse.
+function decode_scanString(s,startPos)
+ base.assert(startPos, 'decode_scanString(..) called without start position')
+ local startChar = string.sub(s,startPos,startPos)
+ base.assert(startChar==[[']] or startChar==[["]],'decode_scanString called for a non-string')
+ local escaped = false
+ local endPos = startPos + 1
+ local bEnded = false
+ local stringLen = string.len(s)
+ repeat
+ local curChar = string.sub(s,endPos,endPos)
+ if not escaped then
+ if curChar==[[\]] then
+ escaped = true
+ else
+ bEnded = curChar==startChar
+ end
+ else
+ -- If we're escaped, we accept the current character come what may
+ escaped = false
+ end
+ endPos = endPos + 1
+ base.assert(endPos <= stringLen+1, "String decoding failed: unterminated string at position " .. endPos)
+ until bEnded
+ local stringValue = 'return ' .. string.sub(s, startPos, endPos-1)
+ local stringEval = base.loadstring(stringValue)
+ base.assert(stringEval, 'Failed to load string [ ' .. stringValue .. '] in JSON4Lua.decode_scanString at position ' .. startPos .. ' : ' .. endPos)
+ return stringEval(), endPos
+end
+
+--- Scans a JSON string skipping all whitespace from the current start position.
+-- Returns the position of the first non-whitespace character, or nil if the whole end of string is reached.
+-- @param s The string being scanned
+-- @param startPos The starting position where we should begin removing whitespace.
+-- @return int The first position where non-whitespace was encountered, or string.len(s)+1 if the end of string
+-- was reached.
+function decode_scanWhitespace(s,startPos)
+ local whitespace=" \n\r\t"
+ local stringLen = string.len(s)
+ while ( string.find(whitespace, string.sub(s,startPos,startPos), 1, true) and startPos <= stringLen) do
+ startPos = startPos + 1
+ end
+ return startPos
+end
+
+--- Encodes a string to be JSON-compatible.
+-- This just involves back-quoting inverted commas, back-quotes and newlines, I think ;-)
+-- @param s The string to return as a JSON encoded (i.e. backquoted string)
+-- @return The string appropriately escaped.
+function encodeString(s)
+ s = string.gsub(s,'\\','\\\\')
+ s = string.gsub(s,'"','\\"')
+-- slug
+-- s = string.gsub(s,"'","\\'")
+ s = string.gsub(s,'\n','\\n')
+ s = string.gsub(s,'\t','\\t')
+ return s
+end
+
+-- Determines whether the given Lua type is an array or a table / dictionary.
+-- We consider any table an array if it has indexes 1..n for its n items, and no
+-- other data in the table.
+-- I think this method is currently a little 'flaky', but can't think of a good way around it yet...
+-- @param t The table to evaluate as an array
+-- @return boolean, number True if the table can be represented as an array, false otherwise. If true,
+-- the second returned value is the maximum
+-- number of indexed elements in the array.
+function isArray(t)
+ -- Next we count all the elements, ensuring that any non-indexed elements are not-encodable
+ -- (with the possible exception of 'n')
+ local maxIndex = 0
+ for k,v in base.pairs(t) do
+ if (base.type(k)=='number' and math.floor(k)==k and 1<=k) then -- k,v is an indexed pair
+ if (not isEncodable(v)) then return false end -- All array elements must be encodable
+ maxIndex = math.max(maxIndex,k)
+ else
+ if (k=='n') then
+ if v ~= table.getn(t) then return false end -- False if n does not hold the number of elements
+ else -- Else of (k=='n')
+ if isEncodable(v) then return false end
+ end -- End of (k~='n')
+ end -- End of k,v not an indexed pair
+ end -- End of loop across all pairs
+ return true, maxIndex
+end
+
+--- Determines whether the given Lua object / table / variable can be JSON encoded. The only
+-- types that are JSON encodable are: string, boolean, number, nil, table and json.null.
+-- In this implementation, all other types are ignored.
+-- @param o The object to examine.
+-- @return boolean True if the object should be JSON encoded, false if it should be ignored.
+function isEncodable(o)
+ local t = base.type(o)
+ return (t=='string' or t=='boolean' or t=='number' or t=='nil' or t=='table') or (t=='function' and o==null)
+end
+
--- /dev/null
+MinuteCount = class(function(obj, minutes)
+ obj.minutes = minutes
+ obj.data = {}
+ obj.current = 0
+end)
+
+function MinuteCount:add()
+ self.current = self.current + 1
+end
+
+function MinuteCount:moveon()
+ table.insert(self.data, 0, self.current)
+ table.remove(self.data, self.minutes)
+ self.current = 0
+end
+
+function MinuteCount:sum()
+ local sum = self.current
+ for _, v in ipairs(self.data) do
+ sum = sum + v
+ end
+
+ return sum
+end
--- /dev/null
+function tablelen(tab)
+ local count = 0
+
+ for _, _ in pairs(tab) do
+ count = count + 1
+ end
+
+ return count
+end
+
+function tablesequal(a, b)
+ local k, v
+
+ for k, v in pairs(a) do
+ if not b[k] or b[k] ~= v then
+ return false
+ end
+ end
+
+ for k, v in pairs(b) do
+ if not a[k] or a[k] ~= v then
+ return false
+ end
+ end
+
+ return true
+end
+
+function trim(s)
+ return (string.gsub(s, "^%s*(.-)%s*$", "%1"))
+end
+
+function explode(d,p,m)
+ t={}
+ ll=0
+ f=0
+ if m and m < 1 then
+ return { p }
+ end
+
+ while true do
+ l=string.find(p,d,ll+1,true) -- find the next d in the string
+ if l~=nil then -- if "not not" found then..
+ table.insert(t, string.sub(p,ll,l-1)) -- Save it in our array.
+ ll=l+1 -- save just after where we found it for searching next time.
+ f = f + 1
+ if m and f >= m then
+ table.insert(t, string.sub(p,ll))
+ break
+ end
+ else
+ table.insert(t, string.sub(p,ll)) -- Save what's left in our array.
+ break -- Break at end, as it should be, according to the lua manual.
+ end
+ end
+ return t
+end
+
+function tableempty(table)
+ for k, v in pairs(table) do
+ return false
+ end
+
+ return true
+end
+
+function urlencode(str)
+ if (str) then
+ str = string.gsub (str, "\n", "\r\n")
+ str = string.gsub (str, "([^%w ])",
+ function (c) return string.format ("%%%02X", string.byte(c)) end)
+ str = string.gsub (str, " ", "+")
+ end
+ return str
+end
+
+function matchtoregex(match)
+ local x = match:gsub("([%^%$%(%)%%%.%[%]%+%-])", "%%%1")
+ x = x:gsub("%*", ".*")
+ x = x:gsub("%?", ".")
+ x = "^" .. x .. "$"
+ return x
+end
+
--- /dev/null
+require("lib/quakenet/access")
+require("lib/quakenet/iterators")
+require("lib/quakenet/legacy")
+require("lib/quakenet/achievements")
+
+function chanmsg(a)
+ irc_smsg(a, "#qnet.keepout")
+end
+
+function crapchanmsg(a)
+ irc_smsg(a, "#qnet.trj")
+end
+
+function statusmsg(a)
+ irc_smsg("dd," .. a, "#qnet.keepout")
+end
+
+function chanmsgn(t)
+ string.gsub(t, "[^\r\n]+", chanmsg)
+end
+
+function noticen(n, t)
+ string.gsub(t, "[^\r\n]+", function(s) irc_notice(n, s) end);
+end
+
+local irc_localrealovmode = irc_localovmode;
+
+function irc_localovmode(source, chan, ...)
+ if type(...) == 'table' then
+ irc_localrealovmode(source, chan, ...)
+ else
+ irc_localrealovmode(source, chan, { ... })
+ end
+end
+
+function irc_localaction(n, c, m)
+ irc_localchanmsg(n, c, string.char(1) .. "ACTION " .. m .. string.char(1))
+end
+
+function irctolower(l)
+ l = l:lower()
+ l = l:gsub("%[", "{")
+ l = l:gsub("%]", "}")
+ l = l:gsub("\\", "|")
+ l = l:gsub("%^", "~")
+ return l
+end
+
+function irc_localregisteruser(nickname, ident, hostname, realname, account, usermodes, handler_function)
+ return irc_localregisteruserid(nickname, ident, hostname, realname, account, 0, usermodes, handler_function)
+end
--- /dev/null
+function onstaff(nick)
+ return irc_nickonchan(nick, "#qnet.staff")
+end
+
+function ontlz(nick)
+ return irc_nickonchan(nick, "#twilightzone")
+end
+
+
--- /dev/null
+require "lib/class"
+
+Achievements = class(function(obj, bot_id, types)
+ obj.bot_id = bot_id
+ obj.types = types
+end)
+
+function Achievements:send(nick, type, ...)
+ local extra = ...
+ if not extra then
+ extra = 0
+ end
+
+ local ntype = self.types[type]
+ if ntype then
+ type = ntype
+ end
+
+ local user_numeric = irc_numerictobase64(nick.numeric)
+ irc_privmsg("C", "EXTACH " .. user_numeric .. " " .. self.bot_id .. " " .. type .. " " .. extra)
+end
--- /dev/null
+nickpusher = { nick = 0, ident = 1, hostname = 2, realname = 3, authname = 4, ipaddress = 5, numeric = 6, timestamp = 7, accountts = 8, umodes = 9, country = 10 }
+chanpusher = { name = 0, totalusers = 1, topic = 2, realusers = 3, timestamp = 4, modes = 5 }
+
+function channelusers_iter(channel, items)
+ local t = { irc_getfirstchannick(channel, items) }
+ if #t == 0 then
+ return function()
+ return nil
+ end
+ end
+ local b = false
+
+ return function()
+ if not b then
+ b = true
+ return t
+ end
+
+ local t = { irc_getnextchannick() }
+ if #t == 0 then
+ return nil
+ end
+ return t
+ end
+end
+
+function channelhash_iter(items)
+ local t = { irc_getfirstchan(items) }
+ if #t == 0 then
+ return function()
+ return nil
+ end
+ end
+ local b = false
+
+ return function()
+ if not b then
+ b = true
+ return t
+ end
+
+ local t = { irc_getnextchan() }
+ if #t == 0 then
+ return nil
+ end
+ return t
+ end
+end
+
+function nickhash_iter(items)
+ local t = { irc_getfirstnick(items) }
+ if #t == 0 then
+ return function()
+ return nil
+ end
+ end
+ local b = false
+
+ return function()
+ if not b then
+ b = true
+ return t
+ end
+
+ local t = { irc_getnextnick() }
+ if #t == 0 then
+ return nil
+ end
+ return t
+ end
+end
+
+function nickchannels_iter(nick)
+ local c = irc_getnickchancount(nick)
+ local i = -1
+
+ return function()
+ i = i + 1
+ if i == c then
+ return nil
+ end
+
+ return irc_getnickchanindex(nick, i)
+ end
+end
+
+
--- /dev/null
+local function flatten(x)
+ local resulta = {}
+ local resultb = {}
+
+ for k, v in pairs(x) do
+ table.insert(resulta, k)
+ table.insert(resultb, v)
+ end
+ return resulta, resultb
+end
+
+local inickpusher, nnickpusher = flatten(nickpusher)
+local ichanpusher, nchanpusher = flatten(chanpusher)
+
+local function __nickpush(fn, input)
+ local n = { fn(input, nnickpusher) }
+ if #n == 0 then
+ return
+ end
+
+ local result = {}
+ for k, v in pairs(n) do
+ result[inickpusher[k]] = v
+ end
+
+ -- compatibility
+ result.account = result.authname
+ return result
+end
+
+function irc_getnickbynick(nick)
+ return __nickpush(irc_fastgetnickbynick, nick)
+end
+
+function irc_getnickbynumeric(numeric)
+ return __nickpush(irc_fastgetnickbynumeric, numeric)
+end
+
+function irc_getchaninfo(channel)
+ local c = { irc_fastgetchaninfo(channel, nchanpusher) }
+ if #c == 0 then
+ return
+ end
+
+ local result = {}
+ for k, v in pairs(c) do
+ result[ichanpusher[k]] = v
+ end
+
+ return result
+end
+
+function irc_localregisteruser(nickname, ident, hostname, realname, account, usermodes, handler_function)
+ return irc_localregisteruserid(nickname, ident, hostname, realname, account, 0, usermodes, handler_function)
+end
--- /dev/null
+local sched = {}
+local tag = 0
+
+local function timesort(a, b)
+ return a[1] < b[1]
+end
+
+function doschedule()
+ local ct = os.time()
+ local out = {}
+ local callers = {}
+
+ for t, e in pairs(sched) do
+ if t <= ct then
+ table.insert(callers, { t, e })
+ else
+ out[t] = e
+ end
+ end
+ table.sort(callers, timesort)
+
+ sched = out
+
+ for _, e in ipairs(callers) do
+ for _, v in pairs(e[2]) do
+ if v[1] then
+ if v[2] then
+ v[1](unpack(v[2]))
+ else
+ v[1]()
+ end
+ else
+ scripterror("schedule.lua: event is nil!")
+ end
+ end
+ end
+end
+
+function schedule(when, callback, ...)
+ tag = tag + 1
+ local n = { callback, { ... }, tag }
+ local w = os.time() + when
+
+ if sched[w] then
+ table.insert(sched[w], n)
+ else
+ sched[w] = { n }
+ end
+ return { w, tag }
+end
+
+function delschedule(tag)
+ local c = {}
+
+ local w, o = unpack(tag)
+ if not sched[w] then
+ return
+ end
+
+ for i, v in pairs(sched[w]) do
+ if v[3] == o then
+ if #sched[w] == 1 then
+ sched[w] = nil
+ else
+ sched[w][i] = nil
+ end
+ return
+ end
+ end
+end
+
+function scheduleempty()
+ return tableempty(sched)
+end
--- /dev/null
+function savetable(filename, t)
+ local f = assert(io.open(filename, "w"))
+
+ f:write("return\n")
+
+ serialise(f, t)
+
+ f:close()
+end
+
+function loadtable(filename)
+ local func, err = loadfile(filename)
+
+ if not func then
+ return nil
+ end
+
+ return func()
+end
+
+function serialise(f, o)
+ if type(o) == "number" then
+ f:write(o)
+ elseif type(o) == "string" then
+ f:write(string.format("%q", o))
+ elseif type(o) == "boolean" then
+ if o == true then
+ f:write("true")
+ else
+ f:write("false")
+ end
+ elseif type(o) == "table" then
+ f:write("{\n")
+ for k,v in pairs(o) do
+ f:write(" [")
+ serialise(f, k)
+ f:write("] = ")
+ serialise(f, v)
+ f:write(",\n")
+ end
+ f:write("}\n")
+ elseif type(o) == "nil" then
+ f:write("nil")
+ else
+ error("cannot serialise a " .. type(o))
+ end
+end
+
+function loadtextfile(file, fn)
+ local f = io.open(file, "r")
+ if not f then
+ return false
+ end
+
+ while true do
+ local line = f:read()
+ if line == nil then
+ return true
+ end
+
+ fn(line)
+ end
+end
--- /dev/null
+-- transparent non-blocking writes with buffers!
+
+local socket_write_raw = socket_write
+local socket_unix_connect_raw = socket_unix_connect
+local socket_unix_bind_raw = socket_unix_bind
+local socket_tcp_connect_raw = socket_tcp_connect
+local socket_tcp_bind_raw = socket_tcp_bind
+local socket_udp_connect_raw = socket_udp_connect
+local socket_udp_bind_raw = socket_udp_bind
+local socket_close_raw = socket_close
+
+local sockets = {}
+
+function socket_handler(socket, event, tag, ...)
+ if event == "flush" then
+ local buf = sockets[socket].writebuf
+ local ret = socket_write_raw(socket, buf)
+
+ if ret == -1 then
+ return -- close will be called
+ end
+
+ sockets[socket].writebuf = buf:sub(ret + 1)
+
+ if sockets[socket].closing and sockets[socket].writebuf == "" then
+ socket_close(socket)
+ end
+
+ -- no reason to tell the caller
+ return
+ elseif event == "accept" then
+ local newsocket = ...
+ socket_new(newsocket, sockets[socket].handler)
+ end
+
+ sockets[socket].handler(socket, event, tag, ...)
+
+ if event == "close" then
+ sockets[socket] = nil
+ end
+end
+
+function socket_new(socket, handler)
+ sockets[socket] = { writebuf = "", handler = handler }
+end
+
+function socket_unix_connect(path, handler, tag)
+ local connected, socket = socket_unix_connect_raw(path, socket_handler, tag)
+ if connected == nil then
+ return nil
+ end
+
+ socket_new(socket, handler)
+ if connected then
+ socket_handler(socket, "connect", tag)
+ end
+
+ return socket
+end
+
+function socket_unix_bind(path, handler, tag)
+ local socket = socket_unix_bind_raw(path, socket_handler, tag)
+ if not socket then
+ return nil
+ end
+
+ socket_new(socket, handler)
+ return socket
+end
+
+local function socket_ip_connect(fn, address, port, handler, tag)
+ local connected, socket = fn(address, port, socket_handler, tag)
+ if connected == nil then
+ return nil
+ end
+
+ socket_new(socket, handler)
+ if connected then
+ socket_handler(socket, "connect", tag)
+ end
+
+ return socket
+end
+
+local function socket_ip_bind(fn, address, port, handler, tag)
+ local socket = fn(address, port, socket_handler, tag)
+ if not socket then
+ return nil
+ end
+
+ socket_new(socket, handler)
+ return socket
+end
+
+function socket_tcp_bind(address, port, handler, tag)
+ return socket_ip_bind(socket_tcp_bind_raw, address, port, handler, tag)
+end
+
+function socket_tcp_connect(address, port, handler, tag)
+ return socket_ip_connect(socket_tcp_connect_raw, address, port, handler, tag)
+end
+
+function socket_udp_bind(address, port, handler, tag)
+ return socket_ip_bind(socket_udp_bind_raw, address, port, handler, tag)
+end
+
+function socket_udp_connect(address, port, handler, tag)
+ return socket_ip_connect(socket_udp_connect_raw, address, port, handler, tag)
+end
+
+function socket_write(socket, data)
+ if sockets[socket].writebuf == "" then
+ local ret = socket_write_raw(socket, data)
+ if ret == -1 then
+ return false -- close will be called regardless
+ end
+
+ if ret == data:len() then
+ return true
+ end
+
+ -- lua strings start at 1
+ sockets[socket].writebuf = data:sub(ret + 1)
+ else
+ sockets[socket].writebuf = sockets[socket].writebuf .. data
+ end
+
+ return true
+end
+
+function socket_close(socket, flush)
+ if whenbufferempty and sockets[socket].writebuf ~= "" then
+ sockets[socket].closing = true
+ return
+ end
+
+ return socket_close_raw(socket)
+end